1.网络编程作用
程序能够通过网络与其他计算机上的程序进行数据交换、通信和协作
2.关键概念
两个对象:服务器(被动响应请求),客户端(主动发起请求)。浏览器看b站视频时,浏览器就是客户端,B站那边就是服务器。
两种传输方式(运输层):TCP,面向连接的(三次握手建立连接),数据传输的单位是用户数据报(有序列号、确认号等控制信息)。UDP,无连接的(不确定服务器是否可以接受数据),数据传输的单位是用户数据报(标头中没有确认号等控制信息),不保证可靠的交互。UDP适用于对数据准确性要求不高的场合(游戏,视频通话),丢失几帧数据不影响使用。
数据传输三要素:源(数据发送方),目的(数据接收方),长度
ip:IP地址是一组数字,用于唯一标识互联网上的每个设备。通过IP地址,网络可以找到你的电脑等设备。
端口:进程与外界数据交互的中间接口。范围0-65535,其中0-1023是系统保留端口(一些特定应用预留端口),1024-65535是动态或私有端口。80端口是http服务,22是ssh服务,https默认端口是443。https://zhuanlan.zhihu.com/p/225777212
socket:一种通用传输层网络接口(实际代码),方便实现TCP、UDP等连接。
僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵尸进程。僵尸进程还会消耗一定的系统资源。解决方法:具体做法是接管SIGCHLD信号。子进程死后,会发送SIGCHLD信号给父进程,父进程收到此信号后,执行waitpid()函数为子进程收尸。
孤儿进程:父进程如果不等待子进程退出,在子进程之前就结束了自己的“生命”此时的子进程叫做孤儿进程。Linux避免系统存在过多的孤儿进程,init进程收留孤儿进程,变成孤儿进程的父进程。僵尸进程将会导致资源浪费,而孤儿则不会。
3.实现流程
服务器端
1.创建套接字:socket
2.配置网络传输参数:sockaddr_in结构体
3.将地址绑定到套接字:bind
4.监听服务器端连接请求:listen,所有连接请求是保存到一个队列中
5.接收客户端连接请求:accept
6.接发收数据:send/recv
7.关闭套接字:close
客户端
1.创建套接字:socket
2.请求连接服务器:sonnect
3.接发收数据:send/recv
4.代码示例
服务器端
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
/* socket
* bind
* listen
* accept
* send/recv
*/
//需要输入参数客户端ip
#define SERVER_PORT 8888//服务器监听端口号
#define BACKLOG 10//
int main(int argc, char **argv)
{
int iSocketServer;//服务器端描述
int iSocketClient;//客户端描述
struct sockaddr_in tSocketServerAddr;//服务器端地址信息结构体
struct sockaddr_in tSocketClientAddr;//客户端地址信息结构体
int iRet;
int iAddrLen;
int iRecvLen;//服务器接收数据长度
unsigned char ucRecvBuf[1000];//接收客户端数据
int iClientNum = -1;
signal(SIGCHLD,SIG_IGN);//防止子进程成为僵尸进程
//1.配置socket参数
iSocketServer = socket(AF_INET, SOCK_STREAM, 0);//创建一个套接字,AF_INET表示使用IPv4,SOCK_STREAM表示使用TCP协议
if (-1 == iSocketServer)
{
printf("socket error!\n");
return -1;
}
//2.配置网络传输参数ip、端口等
tSocketServerAddr.sin_family = AF_INET;//使用IPv4
tSocketServerAddr.sin_port = htons(SERVER_PORT); /* 端口号 */
tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;//绑定到本地任意 IP 地址
memset(tSocketServerAddr.sin_zero, 0, 8); //将 tSocketServerAddr.sin_zero 数组中的前 8 个字节全部设置为 0。
//3.将套接字绑定到刚才配置的地址和端口
iRet = bind(iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
if (-1 == iRet)
{
printf("bind error!\n");
return -1;
}
//4.开始监听连接请求,会将接收到的请求存到一个队列中,队列最大长度为BACKLOG
iRet = listen(iSocketServer, BACKLOG);
if (-1 == iRet)
{
printf("listen error!\n");
return -1;
}
while (1)
{
iAddrLen = sizeof(struct sockaddr);
//5.接受客户端连接请求,队列中多个请求不确定连接哪一个,没有监听到请求会阻塞程序(程序不会执行后面内容)
iSocketClient = accept(iSocketServer, (struct sockaddr *)&tSocketClientAddr, &iAddrLen);
if (-1 != iSocketClient)//连接成功
{
iClientNum++;
//输出连接客户端编号,ip
printf("Get connect from client %d : %s\n", iClientNum, inet_ntoa(tSocketClientAddr.sin_addr));//inet_ntoa将网络字节序ip从二进制转化为十进制
//fork复制出一个进程,
if (!fork())
{
/* 子进程的源码,主进程会执行else的代码(此处主进程不执行) */
while (1)
{
/* 6.获得客户端数据并显示 */
iRecvLen = recv(iSocketClient, ucRecvBuf, 999, 0);//接收客户端的数据并保存到ucRecvBuf中,最长为999字节,通过特定字符判断一次数据是否接收完成
if (iRecvLen <= 0)
{
close(iSocketClient);
return -1;
}
else
{
ucRecvBuf[iRecvLen] = '\0';
printf("Get Msg From Client %d: %s\n", iClientNum, ucRecvBuf);
}
}
}
}
}
//7.关闭套接字
close(iSocketServer);
return 0;
}
客户端
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
/* socket
* connect
* send/recv
*/
//使用时需要输入参数ip
#define SERVER_PORT 8888
int main(int argc, char **argv)
{
int iSocketClient;
struct sockaddr_in tSocketServerAddr;
int iRet;
unsigned char ucSendBuf[1000];
int iSendLen;
//需要传入服务器ip参数
if (argc != 2)
{
printf("Usage:\n");
printf("%s <server_ip>\n", argv[0]);
return -1;
}
//1.创建套接字,AF_INET表示使用IPv4,SOCK_STREAM表示使用TCP
iSocketClient = socket(AF_INET, SOCK_STREAM, 0);
//2.填充服务器地址信息
tSocketServerAddr.sin_family = AF_INET;//使用IPv4
tSocketServerAddr.sin_port = htons(SERVER_PORT); /* 将端口号由主机字节序转化为网络字节序 */
//tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
if (0 == inet_aton(argv[1], &tSocketServerAddr.sin_addr))//将点分十进制的IP地址转换为网络字节序
{
printf("invalid server_ip\n");
return -1;
}
memset(tSocketServerAddr.sin_zero, 0, 8);// 清空填充字段
//2.连接服务器
iRet = connect(iSocketClient, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
if (-1 == iRet)
{
printf("connect error!\n");
return -1;
}
while (1)
{
if (fgets(ucSendBuf, 999, stdin))//从标准输入里获取数据并保存到usSendBuf中,最长为999
{
//3.发送数据给客户端
iSendLen = send(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0);
if (iSendLen <= 0)
{
//4.关闭套接字
close(iSocketClient);
return -1;
}
}
}
return 0;
}
5.问题总结
1.端口、程序、进程关系,多进程监听同一端口(多个网络应用进程监听80端口)如何区分是那个进程的数据
2.IP地址是如何被分配的
3.如何解决僵尸进程问题
添加语句signal(SIGCHLD,SIG_IGN)