精华内容
下载资源
问答
  • 网络编程 套接字

    2021-04-20 13:49:25
    网络编程 套接字 网络编程就是编写程序使两台联网的计算机相互交换数据。两台计算机进行数据传输时,首先要进行物理连接,在联网情况下就不用担心这一点。此时要进行网络编程只需要考虑编写数据传输软件,操作系统会...

    网络编程 套接字

    网络编程就是编写程序使两台联网的计算机相互交换数据。两台计算机进行数据传输时,首先要进行物理连接,在联网情况下就不用担心这一点。此时要进行网络编程只需要考虑编写数据传输软件,操作系统会提供名为“套接字”(socket)的部件,套接字是网络数据传输用的软件设备。网络编程又称套接字编程。

    网络编程中接受连接请求的套接字创建过程可整理如下

    1. 调用socket函数创建套接字在这里插入图片描述
    2. 调用bind函数分配IP地址和端口号
      在这里插入图片描述
    3. 调用listen函数转为可接受请求状态
      在这里插入图片描述
    4. 调用accept函数受理连接请求
      在这里插入图片描述
    展开全文
  • 网络编程套接字

    千次阅读 2018-08-08 23:52:31
    一个套接字网络连接的一个端点。每个套接字都有相应的套接字地址,由一个32位的因特网地址和一个16位的端口号组成,用“IP地址:端口号”来表示,如:192.168.181.129:9999,其中“192.168.181.129”表示IP地址,...

    一、套接字的概念
    一个套接字是网络连接的一个端点。每个套接字都有相应的套接字地址,由一个32位的因特网地址和一个16位的端口号组成,用“IP地址:端口号”来表示,如:192.168.181.129:9999,其中“192.168.181.129”表示IP地址,“9999”表示端口号。

    二、预备知识
    1、认识IP地址
    (1)IP协议有两个版本,IPv4和IPv6,现在用得比较多的是IPv4。
    (2)IP地址是在IP协议中用来标识网络中不同主机的地址。
    (3)对于IPv4版本来说,IP地址是一个4字节,32位的整数。
    (4)通常使用“点分十进制”的字符串表示IP地址,例如:“192.168.181.129”,其中用点分割的每一个数字表示一个字节,范围为0-255。
    (5)在IP数据报头部,有两个IP地址,分别叫做元IP地址和目的IP地址。

    有了IP地址能够把信息发送到对方的机器上,但是还需要一个其他的标识符来区分这个数据要给哪个程序进行解析,它就是端口号。

    2、认识端口号
    (1)端口号是具有网络功能的应用软件的标识号。
    (2)端口号用来标识一个进程,告诉操作系统当前的这个数据要交给哪一个进程来处理。
    (3)端口号是一个2字,16位的整数,可以标识的范围是0-65535。其中,0-1023是公认端口号,即已经公认定义或为将要公认定义的软件保留的,而1024-65535是并没有公共定义的端口号,用户可以自己定义这些端口的作用。
    (4)IP地址+端口号能够标识网络上的某一台主机的某一个进程。
    (5)一个进程能够占用多个端口号,但是一个端口号只能被一个进程占用。

    三、套接字接口
    套接字接口是一组函数,用以创建网络应用,存放在“sys/socket.h”函数库中。
    1、socket函数
    (1)函数功能:创建一个套接字描述符。
    (2)函数原型:int socket(int demain, int type, int protocol)
    (3)参数:
    1)domain:地址域(版本号IPV4),通常使用AF_INET表示32位IP地址。
    2)type:套接字类型,SOCK_STREAM表字节流类型(TCP);SOCK_DGRAM表数据报类型(UDP)。
    3)protocol:协议,IPPROTO_TCP表示TCP协议;IPPROTO_UDP表示UDP协议;“0”表示接受任何协议。
    (4)返回值:若成功返回0,失败返回-1。
    (5)释:socket函数旨在打开一个网络通讯端口,如果成功就像open一样返回一个文件描述符,应用程序可以像读写文件一样用read/write在网络上收发数据。

    2、bind函数
    (1)函数功能:将套接字地址和套接字描述符联系起来。
    (2)函数原型:int bind(int sockfd, const struct socketaddr *serveraddr, socklen_t addrlen)。
    (3)参数:
    1)sockfd:套接字描述符。
    2)addr:套接字的地址。
    3)addlen:IPv4结构体的大小,即sizeof(sockaddr_in)。
    (4)返回值:成功返回0,失败返回-1。
    (5)初始化:

    struct sockaddr_in serveraddr
    Bzero(&serveraddr, sizeof(serveraddr));
    Serveraddr.sin_family = AF_INET;
    Serveraddr.sin_port = htons(SERV_PORT);
    Serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
    

    1)bzero表示将整个结构体清零。
    2)网络地址为INADDR_ANY表示本地的任意IP地址。因为服务器可能有多个网卡,每个网卡也可能绑定多个IP地址,这样设置可以在所有的IP地址上监听,直到某个客户端建立了连接释才确定下来到底用哪个IP地址。
    3)端口号SERV_PORT在这里定义为9999。
    (6)释:
    1)服务器程序所监听的网络端口号和网络地址通常是固定不变,客户端得知服务器程序的地址和端口号后就可以向服务器发起连接请求。所以服务器需要绑定一个固定的网络地址和端口号。
    2)“struct sockaddr*”是通用指针类型,实际上serveraddr参数可以接受多种协议的sockaddr结构体,所以需要第三个参数addrlen指定结构体的长度。
    (7)注意:
    1)客户端不是不允许调用bind函数,只是没有必要调用bind函数固定一个端口。否则,如果在同一台机器上启动多个客户端,就会出现端口号被占用导致不能正确建立连接。
    2)服务器也不是必须调用bind函数,但是如果服务器不调用bind函数,内核就会自动给服务器分配监听端口,每次启动服务器时端口号都不一样,客户端要连接服务器就会遇到麻烦。

    3、listen函数
    (1)函数功能:服务器告知内核,套接字被服务器使用了,转化为监听套接字,接受客户端的连接请求。
    (2)函数原型:int listen(int sockfd, int backlog)。
    (3)参数:
    1)sockfd:套接字描述符。
    2)backlog:表示同一时间最大的并发数。
    (4)返回值:成功返回0,失败返回-1。
    (5)释:
    listen函数声明sockfd处于监听状态,并且最多允许有backlog个客户端处于连接等待状态,如果接收到更多的连接请求就忽略。一般backlog的值不会太大,backlog的设置主要是为了提高客户端与服务器的连接效率,太大了会消耗很多内存,得不偿失。

    4、connect函数
    (1)函数功能:客户端通过调用connect函数来建立和服务器的连接。
    (2)函数原型:int connect(int clientfd, const struct sockaddr *serve_addr, socklen_t addrlen)。
    (3)参数:
    1)clientfd:客户端套接字描述符。
    2)serve_addr:服务器套接字地址。
    3)addrlen:IPv4结构体的大小,即sizeof(struct sockaddr_in)。
    (4)返回值:成功返回0,失败返回-1。
    (5)释:
    connect函数和bind函数的参数形式一样,区别在于bind的参数是自己的地址,而connect的参数是对方的地址。

    5、accept函数
    (1)函数功能:服务器通过调用accept函数接收来自客户端的连接请求。
    (2)函数原型:int accept(int listenfd, struct sockaddr* cli_addr, int *addrlen)。
    (3)参数:
    1)sockfd:等待客户端的连接请求侦听描述符。
    2)cli_addr:客户端套接字地址。
    3)addrlen:套接字地址长度。
    (4)返回值:成功返回非负侦听描述符,失败返回-1。
    (5)释:
    1)三次握手完成后,服务器调用accept函数接受连接。
    2)服务器如果调用accept函数时还没有客户端的连接请求,就阻塞等待直到客户端连接上来。
    3)addr是一个传出参数,accept函数返回时传出客户端的地址和端口号。
    4)如果给addr参数传NULL,则表示不关心客户端的地址。
    5)Addrlen参数是一个传入传出参数,传入的是调用者提供的,缓冲区addr的长度以避免缓冲区溢出的问题,传出的是客户端地址结构的实际长度。

    四、套接字地址结构
    1、sockaddr结构
    sockaddr结构是套接字的总体结构,包括16位的地址类型和14字节的地址数据,如下图所示。
    这里写图片描述
    在connect、bind和accept函数中要求一个指向与协议相关的的套接字地址结构的指针。如何能接受各种类型的套接字地址结构?解决的办法就是定义套接字函数要求一个指向通用sockaddr结构的指针,然后要求应用程序将与协议特定的结构的指针强制转换成这个通用的指针结构。

    2、sockaddr_in结构
    这里写图片描述
    IPv4地址用sockaddr_in结构体表示,包括16位的地址类型、16位的端口号和32位的IP地址,其中IPv4的地址用AF_INET表示。sockaddr_in结构体内容如下所示:

    struct sockaddr_in {
    short int           sin_family;  /* Address family */
    unsigned short int  sin_port;    /* Port number */
    struct in_addr      sin_addr;   /* Internet address */
    unsigned char       sin_zero[8];  /* Same size as struct sockaddr */
    };
    

    (1)sin_family:指代协议族,用AF_INET指代IPv4。
    (2)sin_port:端口号,要使用网络字节序(大端存储)。
    (3)sin_addr:IP地址,使用in_addr这个数据结构。

    struct in_addr {
    unsigned long s_addr;
    };
    

    释:in_addr用来表示IPv4的IP地址,其实就是一个32位的整数。
    (4)sin_zero :是为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节。
    注意:sockaddr_in和sockaddr是并列的结构,指向sockaddr_in的结构体的指针也可以指向sockadd的结构体,并代替它。也就是说,可以使用sockaddr_in建立你所需要的信息,最后再进行类型转换就可以了。

    五、数据的转换
    1、网络字节序
    (1)概念:
    网络字序指数据在网络中的存储方式。内存中的多字节数据对于内存地址有大端小端之分,磁盘文件中的多字节数据对于文件偏移量有大端小端之分,网络数据流同样有大端小端之分。TCP/IP协议规定:网络数据流应采用大端存储,即高字节低地址。
    (2)字节顺序转换函数
    为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换,它们存在<arpa/inet.h>头文件中。

    1)uint32_t htonl(uint32_t hostlong);
    2)uint16_t htons(uint16_t hostshort);
    3)uint32_t ntohl(uint32_t netlong);
    4)uint16_t ntohs(uint16_t netshort);
    

    释:h表示host,n表示network,l表示32位长整数,s表示16位短整数,to表示转换。即:htonl表示将32位整数由主机字节序转换为网络字节序,返回网络字节序的值;ntohl表示将32位整数由网络字节序转换为主机字节序,返回主机字节序的值。htons和ntohs函数为16位无符号整数执行相应的转换。

    2、地址转换函数
    网络字节顺序的IP地址是二进制的数据,为了方便使用需要转换为点分十进制的字符串。例如:128.2.194.242就是地址0x8002c2f2的点分十进制表示。应用程序可以使用以下库函数实现IP地址与点分十进制串的转换,它们存放在<arpa/inet.h>头文件中。

    (1)int inet_pton(AF_INET, const char *src, void *dst);

    释:该函数将一个点分十进制串转换为一个二进制的网络字节顺序的IP地址。如果src没有指向一个合法的点分十进制字符串,那么该函数返回0。成功返回1,失败返回-1。

    (2)const char* inet_ntop(AF_INET, const void* src, char* dst,socklen_t size);

    释:该函数将一个二进制的网络字节顺序的IP地址转换为它对应的点分十进制的字符串,并把得到的以null结尾的字符串复制到dst。成功返回指向点分十进制的指针,失败返回NULL。

    【例1】

    #include<stdio.h>
    #include<unistd.h>
    #include<stdlib.h>
    #include<arpa/inet.h>
    
    int main()
    {
    	struct sockaddr_in addr;
    	inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
    	uint32_t* ptr = (uint32_t *)(&addr.sin_addr);
    	socklen_t len = sizeof(struct sockaddr_in);
    	char arr[1024];
    	printf("addr:%x\n", *ptr);
    	printf("addr_str:%s\n", inet_ntop(AF_INET, &addr.sin_addr, arr, len));
    	return 0;
    }
    

    运行结果:
    这里写图片描述
    (3)int inet_aton(const char *string, struct in_addr* addr);
    释:输入参数string包含ASCII表示的IP地址, 输出参数addr是将要用新的IP地址更新的结构。如果输入地址不正确,则返回0;如果成功,返回非零;如果失败,返回-1。
    (4)char *inet_ntoa(struct in_addr in);
    释:该函数在内部申请了一块空间保存返回的点分十进制的IP地址。
    (5)in_addr_t inet_addr(const char *cp);
    释:该函数可将点分十进制的字符串转换为长整型。

    六、套接字应用
    1、UDP
    套接字基于UDP协议的工作过程,如下图所示:
    这里写图片描述
    【例2】通过最简单的客户端/服务器程序,实现一个简单的阻塞式网络聊天工具

    //服务器
    //服务器的作用是与客户端连接,实现数据通信
    #include<stdio.h>
    #include<stdlib.h>
    #include<unistd.h>
    #include<errno.h>
    #include<string.h>
    #include<sys/socket.h>
    #include<netinet/in.h>
    #include<arpa/inet.h>
    
    int main(int argc, char* argv[])
    {
    	if(argc != 3){
    		printf("Please enter ./serve IP port\n");
    		return -1;
    	}
    
    	int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    	if(sockfd < 0){
    		perror("socket error!\n");
    		return -1;
    	}
    
    	struct sockaddr_in localaddr;
    	localaddr.sin_family = AF_INET;
    	localaddr.sin_port = htons(atoi(argv[2]));
    	localaddr.sin_addr.s_addr = inet_addr(argv[1]);
    
    	socklen_t len = sizeof(localaddr);
    	int ret = bind(sockfd, (struct sockaddr*)&localaddr, len);
    	if(ret < 0){
    		perror("bind error!\n");
    		return -1;
    	}
    
    	char buff[1024];
    	struct sockaddr_in clientaddr;
    	while(1){
    	socklen_t clientlen = sizeof(clientaddr);
    	ssize_t s = recvfrom(sockfd, buff, sizeof(buff)-1, 0, (struct sockaddr*)&clientaddr, &clientlen);
    	if(s > 0){
    			buff[s] = 0;
    			printf("recv :%s\n", buff);
    			sendto(sockfd, buff, strlen(buff), 0, (struct sockaddr*)&clientaddr, sizeof(clientaddr));
    	}
    
    	}
    	close(sockfd);
    	return 0;
    }
    
    
    //客户端
    #include<stdio.h>
    #include<stdlib.h>
    #include<unistd.h>
    #include<string.h>
    #include<errno.h>
    #include<sys/socket.h>
    #include<netinet/in.h>
    #include<arpa/inet.h>
    
    int main(int argc, char* argv[])
    {
    	int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    	if(sockfd < 0){
    		perror("socket error!\n");
    		return -1;
    	}
    
    	struct sockaddr_in server;
    	server.sin_family = AF_INET;
    	server.sin_port = htons(atoi(argv[2]));
    	server.sin_addr.s_addr = inet_addr(argv[1]);
    
    	char buf[1024];
    	struct sockaddr_in peer;
    	while(1){
    		socklen_t len = sizeof(peer);
    		printf("Please enter:");
    		fflush(stdout);
    		ssize_t s = read(0, buf, sizeof(buf)-1);
    		if(s > 0){
    			buf[s] = 0;
    			sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr*)&server, sizeof(server));
    			ssize_t _s = recvfrom(sockfd, buf, sizeof(buf)-1, 0, (struct sockaddr*)&peer, &len);
    			if(_s > 0){
    				buf[_s - 1] = 0;
    				printf("server echo:%s\n", buf);
    			}
    		}
    	}
    	close(sockfd);
    	return 0;
    }
    

    运行结果:
    这里写图片描述
    释:
    1)socket的参数使用SOCK_DGRAM表示UDP。
    2)bind之后就可以直接进行通信了。
    3)UDP使用recvfrom()函数接收数据。recvfrom函数原型为:ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr * from, size_t *addrlen),前三个参数等同于函数read()的前三个参数,flags参数是传输控制标志。最后两个参数类似于accept的最后两个参数,接收哪里的数据,及数据大小。
    4)UDP使用sendto()函数发送数据。sendto函数原型为:ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr * to, int addrlen),前三个参数等同于函数write()的前三个参数,flags参数是传输控制标志。最后两个参数类似于connect的最后两个参数,发送数据的目的地,及数据大小。
    5)atoi是一个把字符串转换为整形数的函数。

    2、TCP
    套接字基于TCP协议的工作过程如下图所示:
    这里写图片描述
    【例3】

    //服务器
    #include<stdio.h>
    #include<stdlib.h>
    #include<unistd.h>
    #include<string.h>
    #include<errno.h>
    #include<sys/socket.h>
    #include<netinet/in.h>
    #include<arpa/inet.h>
    
    #define _PORT_ 9999
    #define _BACKLOG_ 10
    
    int main()
    {
    	int sock = socket(AF_INET, SOCK_STREAM, 0);
    	if(sock < 0){
    		printf("create socket error, errno is: %d, errstring is: %s\n", errno, strerror(errno));
    	}
    
    	struct sockaddr_in server_socket;
    	struct sockaddr_in client_socket;
    	bzero(&server_socket, sizeof(server_socket));
    	server_socket.sin_family = AF_INET;
    	server_socket.sin_port = htons(_PORT_);
    	server_socket.sin_addr.s_addr = htonl(INADDR_ANY);
    
    	if(bind(sock, (struct sockaddr*)&server_socket, sizeof(struct sockaddr_in)) < 0){
    		printf("bind error, error code is: %d, error string is:%s\n", errno, strerror(errno));
    		close(sock);
    		return 1;
    	}
    
    	if(listen(sock, _BACKLOG_) < 0){
    		printf("listen error, error code is:%d, error string is: %s\n", errno, strerror(errno));
    		close(sock);
    		return 2;
    	}
    
    	printf("bind and listen success, wait accept...\n");
    
    	for(; ;){
    		socklen_t len = 0;
    		int client_sock = accept(sock, (struct sockaddr*)&client_socket, &len);
    		if(client_sock < 0){
    			printf("accept error, errno is: %d, error string is; %s\n", errno, strerror(errno));
    			close(sock);
    			return 3;
    		}
    		char buf_ip[INET_ADDRSTRLEN];
    		memset(buf_ip, '\0', sizeof(buf_ip));
    		inet_ntop(AF_INET, &client_socket.sin_addr, buf_ip, sizeof(buf_ip));//存放客户端套接字的地址
    
    		printf("get connect, ip is: %s, port is: %d\n", buf_ip, ntohs(client_socket.sin_port));
    		while(1){
    			char buf[1024];
    			memset(buf, '\0', sizeof(buf));
    			read(client_sock, buf, sizeof(buf));
    			printf("client say:%s\n", buf);
    			
    			printf("server say:");
    			memset(buf, '\0', sizeof(buf));
    			fgets(buf, sizeof(buf), stdin);
    			buf[strlen(buf)-1] = '\0';
    			write(client_sock, buf, strlen(buf)+1);
    			printf("plase wait...\n");
    		}
    	}
    	close(sock);
    	return 0;
    }
    
    
    //客户端
    #include<stdio.h>
    #include<stdlib.h>
    #include<unistd.h>
    #include<errno.h>
    #include<string.h>
    #include<sys/socket.h>
    #include<netinet/in.h>
    #include<arpa/inet.h>
    
    #define SERVER_PORT 9999
    #define SERVER_IP "192.168.181.129"
    
    int main(int argc, char *argv[])
    {
    	if(argc != 2){
    		printf("Usage: /client IP\n");
    		return 1;
    	}
    	char *str = argv[1];
    	char buf[1024];
    	memset(buf, '\0', sizeof(buf));
    
    	struct sockaddr_in server_sock;
    	int sock = socket(AF_INET, SOCK_STREAM, 0);
    	bzero(&server_sock, sizeof(server_sock));
    	server_sock.sin_family = AF_INET;
    	inet_pton(AF_INET, SERVER_IP, &server_sock.sin_addr);
    	server_sock.sin_port = htons(SERVER_PORT);
    
    	int ret = connect(sock, (struct sockaddr*)&server_sock, sizeof(server_sock));
    	if(ret < 0){
    		printf("connect failed..., error code is:%d, error string is:%s\n", errno, strerror(errno));
    		return 1;
    	}
    	printf("connect success...\n");
    	while(1){
    		printf("client say:");
    		fgets(buf, sizeof(buf), stdin);
    		buf[strlen(buf)-1] = '\0';
    		write(sock, buf, sizeof(buf));
    		if(strncasecmp(buf, "quit", 4) == 0){
    			printf("quit!\n");
    			break;
    		}
    		printf("please wait...\n");
    		read(sock, buf, sizeof(buf));
    		printf("server say:%s\n", buf);
    	}
    	close(sock);
    	return 0;
    }
    

    (1)测试程序
    这里写图片描述
    释:
    1)图中可看出server程序的9999端口。
    2)netstat命令用来打印Linux中网络系统的状态信息,可让你得知整个Linux系统的网络情况。参数为:
    A、-a或–all:显示所有连线中的Socket;
    B、-l或–listening:显示监控中的服务器的Socket;
    C、-t或–tcp:显示TCP传输协议的连线状况;
    D、-n或–numeric:直接使用ip地址,而不通过域名服务器;
    E、-p或–programs:显示正在使用Socket的程序识别码和程序名称;
    (2)运行结果:
    这里写图片描述
    若再启动一个客户端尝试连接服务器,发现第二个客户端无法与服务器连接成功。因为调用accept接受一个请求之后,就在while循环里一直尝试read,没有调用accept函数接受客户端的连接请求,而导致连接失败。

    解决方法:
    1)通过每个请求,创建子进程的方式来支持多连接,如【例4】。
    2)通过每个请求,创建一个线程的方式来支持多连接,如【例5】。

    【例4】简单的TCP多进程网络程序

    #include<stdio.h>
    #include<stdlib.h>
    #include<unistd.h>
    #include<errno.h>
    #include<string.h>
    #include<sys/socket.h>
    #include<arpa/inet.h>
    #include<netinet/in.h>
    #include<sys/wait.h>
    
    void ProcessRequest(int client_fd, struct sockaddr_in* client_addr)
    {
    	char buf[1024];
    	for( ; ;){
    		ssize_t read_size = read(client_fd, buf, sizeof(buf));
    		if(read_size < 0){
    			perror("read error!\n");
    			continue;
    		}
    		if(read_size == 0){
    			printf("client: %s say bye!\n", inet_ntoa(client_addr->sin_addr));
    			close(client_fd);
    			break;
    		}
    		buf[read_size] = '\0';
    		printf("client %s say: %s\n", inet_ntoa(client_addr->sin_addr), buf);
    		write(client_fd, buf, strlen(buf));
    	}
    	return;
    }
    
    void CreateWorker(int client_fd, struct sockaddr_in* client_addr)
    {
    	pid_t pid = fork();
    	if(pid < 0){
    		perror("fork error!\n");
    		return ;
    	}else if(pid == 0){
    		//child
    		if(fork() == 0){
    			//grand_child
    			ProcessRequest(client_fd, client_addr);
    		}
    		exit(0);
    	}else{
    		//father
    		close(client_fd);
    		waitpid(pid, NULL, 0);
    	}
    }
    
    int main(int argc, char* argv[])
    {
    	if(argc != 3){
    		printf("Usage: ./pid_server IP PORT");
    		return 1;
    	}
    	struct sockaddr_in addr;
    	addr.sin_family = AF_INET;
    	addr.sin_port = htons(atoi(argv[2]));
    	addr.sin_addr.s_addr = inet_addr(argv[1]);
    
    	int fd = socket(AF_INET, SOCK_STREAM, 0);
    	if(fd < 0){
    		perror("scoket error!\n");
    		return 1;
    	}
    
    	int ret = bind(fd, (struct sockaddr*)&addr, sizeof(addr));
    	if(ret < 0){
    		perror("bind error!\n");
    		return 1;
    	}
    
    	ret = listen(fd, 5);
    	if(ret < 0){
    		perror("listen error!\n");
    		return 1;
    	}
    
    	for(; ; ){
    		struct sockaddr_in client_addr;
    		socklen_t len = sizeof(client_addr);
    		int client_fd = accept(fd, (struct sockaddr*)&client_addr, &len);
    		if(client_fd < 0){
    			perror("accept error!\n");
    			continue;
    		}
    		CreateWorker(client_fd, &client_addr);
    	}
    	return 0;
    }
    

    问:接收请求时,先由父进程创建子进程,再由子进程创建孙子进程,然后由孙子进程来处理与客户端的交互,为什么?
    答:假设不创建孙子进程,由子进程处理与客户端的交互,这时父进程一直在等待子进程的退出而不执行它下面的代码,显然是不行的。创建了孙子进程,由孙子进程处理与客户端的交互,子进程退出,父进程回收子进程,孙子进程被init进程领养。

    【例5】简单的TCP多线程网络程序

    #include<stdlib.h>
    #include<stdio.h>
    #include<unistd.h>
    #include<string.h>
    #include<errno.h>
    #include<sys/socket.h>
    #include<arpa/inet.h>
    #include<netinet/in.h>
    #include<pthread.h>
    
    typedef struct Arg
    {
    	int fd;
    	struct sockaddr_in addr;
    }Arg;
    
    void *CreateWorker(void* ptr)
    {
    	Arg* arg = (Arg*)ptr;
    	ProcessRequest(arg->fd, &arg->addr);
    	free(arg);
    	return NULL;
    }
    
    void ProcessRequest(int client_fd, struct sockaddr_in *client_addr)
    {
    	char buf[1024] = {0};
    	for( ; ;){
    		ssize_t read_size = read(client_fd, buf, sizeof(buf));
    		if(read_size < 0){
    			perror("read error!\n");
    			continue;
    		}
    		if(read_size == 0){
    			printf("client: %s say bye!\n", inet_ntoa(client_addr->sin_addr));
    			close(client_fd);
    			break;
    		}
    		buf[read_size] = '\0';
    		printf("client:%s say :%s\n", inet_ntoa(client_addr->sin_addr), buf);
    		write(client_fd, buf, strlen(buf));
    	}
    	return;
    }
    
    
    int main(int argc, char *argv[])
    {
    	if(argc != 3){
    		perror("Usage;./tid_server IP PORT");
    		return 1;
    	}
    
    	struct sockaddr_in addr;
    	addr.sin_family = AF_INET;
    	addr.sin_port = htons(atoi(argv[2]));
    	addr.sin_addr.s_addr = inet_addr(argv[1]);
    
    	int fd = socket(AF_INET, SOCK_STREAM, 0);
    	if(fd < 0){
    		perror("socket error!\n");
    		return 1;
    	}
    
    	int ret = bind(fd, (struct sockaddr*)&addr, sizeof(addr));
    	if(ret < 0){
    		perror("bind error!\n");
    		return 1;
    	}
    	
    	ret = listen(fd, 5);
    	if(ret < 0){
    		perror("listen error!\n");
    		return 1;
    	}
    
    	for(; ;){
    		struct sockaddr_in client_addr;
    		socklen_t len = sizeof(client_addr);
    		int client_fd = accept(fd, (struct sockaddr*)&client_addr, &len);
    		if(client_fd < 0){
    			perror("accept error!\n");
    			continue;
    		}
    		pthread_t tid = 0;
    		Arg* arg = (Arg*)malloc(sizeof(Arg));
    		arg->fd = client_fd;
    		arg->addr = client_addr;
    		pthread_create(&tid, NULL, CreateWorker, (void*)arg);
    		pthread_detach(tid);
    	}
    	return 0;
    }
    
    展开全文
  • 网络编程套接字(二)

    千次阅读 2020-02-11 12:15:44
    网络编程套接字(二)

    网络编程套接字(二)

    一、简单的UDP网络程序

    • 封装udp_socket
    #pragma once
    #include <cstdio>
    #include <cstring>
    #include <string>
    #include <cassert>
    #include <cstdlib>
    
    #include <sys/types.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    typedef struct sockaddr sockaddr;
    typedef struct sockaddr_in sockaddr_in;
    
    class UdpSocket
    {
      public:
        UdpSocket()
          :fd_(-1)
        {}
    
        bool Socket()
        {
          //创建socket操作句柄fd
          fd_ = socket(AF_INET,SOCK_DGRAM,0);
          if(fd_ < 0)
          {
            perror("socket error");
            return false;
          }
    
          return true;
        }
    
        //关闭套节字
        bool Close()
        {
          close(fd_);
          return true;
        }
    
        //绑定端口、IP
        bool Bind(const std::string& ip,uint16_t port)
        {
          sockaddr_in addr;
          addr.sin_family = AF_INET;
          addr.sin_addr.s_addr = inet_addr(ip.c_str());//把点分十进制类型的ip字符串转化为32位的IP地址
          addr.sin_port = htons(port);//把主机字节序转化为网络字节序
    
          int ret  = bind(fd_,(sockaddr*)&addr,sizeof(addr));
          if(ret < 0)
          {
            perror("bind is error");
            return false;
          }
    
          return true;
    
        }
    
        //接收数据
        //出参
        bool RecvFrom(std::string* buf,std::string* ip = nullptr,uint16_t* port = nullptr)
        {
          //出参,由函数调用时填充
          char temp[1024 * 10] = {0};
          sockaddr_in peer;
          socklen_t len = sizeof(peer);
    
          ssize_t read_size = recvfrom(fd_,temp,sizeof(temp) -1,0,(sockaddr*)&peer,&len);
          if(read_size < 0)
          {
            perror("recvfrom is error");
            return false;
          }
    
          buf->assign(temp,read_size);
    
          if(ip != nullptr)
          {
            *ip = inet_ntoa(peer.sin_addr);
          }
          if(port != nullptr)
          {
            *port = ntohs(peer.sin_port);
          }
    
          return true;
        }
    
        //发送数据
        bool SendTo(const std::string& buf,const std::string& ip,uint16_t port)
        {
          sockaddr_in addr;
    
          addr.sin_family = AF_INET;
          addr.sin_addr.s_addr = inet_addr(ip.c_str());
          addr.sin_port = htons(port);
    
          ssize_t write_size = sendto(fd_,buf.data(),buf.size(),0,(sockaddr*)&addr,sizeof(addr));
          if(write_size < 0)
          {
            perror("sendto is error");
            return false;
          }
    
          return true;
        }
      private:
        int fd_;
    };
    
    
    • udp_server.hpp
    #pragma once
    #include <functional>
    #include "udp_socket.hpp"
    
    typedef std::function<void(const std::string&, std::string* resp)> Handler;
    
    class UdpServer
    {
      public:
        UdpServer()
        {
          //在构造函数内部创建sock
          assert(sock_.Socket());
        }
    
        ~UdpServer()
        {
          sock_.Close();
        }
    
        bool Start(const std::string& ip, uint16_t port,Handler handler)
        {
          printf("server is start!\n");
    
          //绑定端口号
          bool ret = sock_.Bind(ip,port);
          if(!ret)
          {
            return false;
          }
    
          //进入死循环,循环处理客户的请求,并计算响应返回结果
          while(1)
          {
            //读取客户端的请求
            std::string req;
            std::string remote_ip;
            uint16_t remote_port;
    
            bool ret = sock_.RecvFrom(&req,&remote_ip,&remote_port);
            if(!ret )
            {
              //如果读取失败尝试循环继续读取,不能直接退出
              continue;
            }
            
            //根据请求经过Handler计算响应,并返回结果
            std::string resp;
            handler(req,&resp);
    
            ret = sock_.SendTo(resp,remote_ip,remote_port);
            printf("[%s : %d] req: %s, resp: %s\n",remote_ip.c_str(),remote_port,req.c_str(),resp.c_str());
          }
    
          sock_.Close();
          return true;
        }
    
      private:
        UdpSocket sock_;
    };
    
    
    • udp_client.hpp
    #pragma once
    #include "udp_socket.hpp"
    
    class UdpClient
    {
      public:
        UdpClient(const std::string& ip,uint16_t port)
          :ip_(ip)
           ,port_(port)
        {
          assert(sock_.Socket());
        }
    
        ~UdpClient()
        {
          sock_.Close();
        }
    
        bool RecvFrom(std::string* buf)
        {
          return sock_.RecvFrom(buf);
        }
    
        bool Sendto(const std::string& buf)
        {
          return sock_.SendTo(buf,ip_,port_);
        }
    
      private:
        UdpSocket sock_;
        
        //服务器端ip、port
        std::string ip_;
        uint16_t port_;
    };
    
    
    • dict_server.cc
    #include "udp_server.hpp"
    #include <iostream>
    #include <unordered_map>
    
    std::unordered_map<std::string,std::string> g_dict;
    
    void Translate(const std::string& req,std::string* resp)
    {
      auto it = g_dict.find(req);
      if(it == g_dict.end())
      {
        *resp = "未查到";
        return;
      }
      *resp = it->second;
    }
    
    #if 0
    int main(int argc,char* argv[])
    {
      if(argc != 3)
      {
        printf("./  ip  ,port\n");
        return 1;
      }
    
      g_dict.insert(std::make_pair("hello", "你好"));
      g_dict.insert(std::make_pair("world", "世界"));
      g_dict.insert(std::make_pair("c++", "最好的编程语言"));
      g_dict.insert(std::make_pair("bit", "特别NB"));
    
      UdpServer server;
      server.Start(argv[1],atoi(argv[2]),Translate);
    
      return 0;
    }
    #endif
    
    int main()
    {
      g_dict.insert(std::make_pair("world", "世界"));
      g_dict.insert(std::make_pair("c++", "最好的编程语言"));
      g_dict.insert(std::make_pair("bit", "特别NB"));
      g_dict.insert(std::make_pair("hello", "您好"));
    
      UdpServer server;
      server.Start("0.0.0.0",9090,Translate);
    
      return 0;
    }
    
    
    • dict_client.cc
    #include "udp_client.hpp"
    #include <iostream>
    
    int main(int argc, char* argv[]) 
    {
      if (argc != 3) 
      {
        printf("Usage ./dict_client [ip] [port]\n");
        return 1;
      }
    
      UdpClient client(argv[1], atoi(argv[2]));
    
      for (;;)
      {
        std::string word;
        std::cout << "请输入您要查的单词: ";
        std::cin >> word;
    
        client.Sendto(word);
    
        std::string result;
        client.RecvFrom(&result);
        std::cout << word << " 意思是 " << result << std::endl;
      }
      return 0;
    }
    
    

    在这里插入图片描述
    在这里插入图片描述

    展开全文
  • 网络编程套接字(三)

    千次阅读 2020-02-11 15:21:12
    网络编程套接字(三) 一、实现简单的Tcp服务器(单用户) tcp_socket.hpp #pragma once #include <cstdio> #include <cstdlib> #include <cstring> #include <cassert> #include <...

    网络编程套接字(三)

    一、实现简单的Tcp服务器(单用户)

    • tcp_socket.hpp
    #pragma once
    #include <cstdio>
    #include <cstdlib>
    #include <cstring>
    #include <cassert>
    #include <string>
    
    #include <unistd.h>
    #include <sys/types.h>
    #include <netinet/in.h>
    #include <sys/socket.h>
    #include <arpa/inet.h>
    #include <fcntl.h>
    
    typedef struct sockaddr sockaddr;
    typedef struct sockaddr_in sockaddr_in;
    
    #define CHECK_RET(exp) if(!(exp)){\
        return false;\
    }
    
    class TcpSocket
    {
      public:
        TcpSocket()
          :fd_(-1)
        {}
    
        TcpSocket(int fd)
          :fd_(fd)
        {}
    
        bool Socket()
        {
          fd_ = socket(AF_INET,SOCK_STREAM,0);
          if(fd_ < 0)
          {
            perror("socket is error");
            return false;
          }
    
          printf("socket fd is successful! fd = %d\n",fd_);
          return true;
        }
    
        bool Close()
        {
          printf("close fd! fd = %d ",fd_);
          close(fd_);
          return true;
        }
    
        bool Bind(const std::string& ip,uint16_t port)
        {
          sockaddr_in addr;
          addr.sin_family = AF_INET;
          addr.sin_addr.s_addr = inet_addr(ip.c_str());
          addr.sin_port = htons(port);
    
          int ret = bind(fd_,(sockaddr*)&addr,sizeof(addr));
          if(ret < 0)
          {
            perror("bind is error");
            return false;
          }
    
          return true;
        }
    
        bool Listen(int num = 5)
        {
          int ret = listen(fd_,num);
          if(ret < 0)
          {
            perror("listen is error");
            return false;
          }
    
          return true;
        }
    
        bool Accept(TcpSocket* peer,std::string* ip = nullptr,uint16_t* port = nullptr)
        {
          sockaddr_in peer_addr;
          socklen_t len = sizeof(peer_addr);
    
          int new_sock = accept(fd_,(sockaddr*)&peer_addr,&len);
          if(new_sock < 0)
          {
            perror("accept is error");
            return false;
          }
    
          printf("accept new fd! fd = %d\n",new_sock);
    
          peer->fd_ = new_sock;
    
          if(ip != nullptr)
          {
            *ip = inet_ntoa(peer_addr.sin_addr); 
          }
          if(port != nullptr)
          {
            *port = ntohs(peer_addr.sin_port);
          }
    
          return true;
        }
    
        int Recv(std::string* buf)
        {
          buf->clear();
          char temp[1024 * 10]= {0};
          
          ssize_t  read_size = recv(fd_,temp,sizeof(temp),0);
          if(read_size < 0)
          {
            perror("recv is error");
            return -1;
          }
          if(read_size == 0)
          {
            return 0;
          }
    
          buf->assign(temp,read_size);
          return 1;
        }
        
        bool Send(const std::string& buf)
        {
          ssize_t write_size = send(fd_,buf.data(),buf.size(),0);
          if(write_size < 0)
          {
            perror("send is error");
            return false;
          }
          return true;
        }
    
        bool Connect(const std::string& ip,uint16_t port)
        {
          sockaddr_in addr;
          addr.sin_family = AF_INET;
          addr.sin_addr.s_addr = inet_addr(ip.c_str());
          addr.sin_port = htons(port);
    
          int ret = connect(fd_,(sockaddr*)&addr,sizeof(addr));
          if(ret < 0)
          {
            perror("connect is error");
            return false;
          }
          return true;
        }
    
        int GetFd()
        {
          return fd_;
        }
      private:
        int fd_;
    };
    
    
    • tcp_server.hpp
    #pragma once
    #include <functional>
    #include "tcp_socket.hpp"
    
    typedef std::function<void (const std::string& req, std::string* resp)> Handler;
    
    class TcpServer 
    {
      public:
        TcpServer(const std::string ip,uint16_t port)
          :ip_(ip)
           ,port_(port)
        {}
    
        bool Start(Handler handler)
        {
          //1.创建socket
          CHECK_RET(listen_sock_.Socket());
    
          //2.绑定端口号
          CHECK_RET(listen_sock_.Bind(ip_,port_));
    
          //3.进行监听
          CHECK_RET(listen_sock_.Listen());
          
          //4.进入死循环处理事件
          //
          while(1)
          {
            //5.进行accept
            TcpSocket new_sock;
            std::string ip;
            uint16_t port;
    
            if(!listen_sock_.Accept(&new_sock,&ip,&port))
            {
              continue;
            }
    
            printf("[client %s : %d ]connect!",ip.c_str(),port);
    
            //6.进行循环读取
            while(1)
            {
              //7.读取请求
              std::string req;
              int ret = new_sock.Recv(&req);
              if(ret < 0)
              {
                printf("[client %s : %d]disconnect!\n",ip.c_str(),port);
                continue;
              }
              if(ret == 0)
              {
                printf("[client %s : %d]关闭了链接!\n",ip.c_str(),port);
                new_sock.Close();
                break;
              }
    
              printf("client say : [%s : %d]\n",ip.c_str(),port);
    
              //8.计算响应
              std::string resp;
              handler(req,&resp);
    
              //9.写回响应
              new_sock.Send(resp);
    
              printf("[%s:%d] req: %s, resp: %s\n", ip.c_str(), port,
                  req.c_str(), resp.c_str());
              
            }
          }
          return true;
        }
    
      private:
        TcpSocket listen_sock_;
    
        std::string ip_;
        uint16_t port_;
    };
    
    
    • tcp_client.hpp
    #pragma once
    #include "tcp_socket.hpp"
    
    class TcpClient 
    {
      public:
        TcpClient(const std::string& ip, uint16_t port) : ip_(ip), port_(port) 
       {
          // [注意!!] 需要先创建好 socket
          sock_.Socket();
          //
        }
    
        ~TcpClient() 
        {
          sock_.Close();
        }
    
        bool Connect() 
        {
          return sock_.Connect(ip_, port_);
        }
    
        bool Recv(std::string* buf) 
        {
          return sock_.Recv(buf);
        }
    
        bool Send(const std::string& buf) 
        {
          return sock_.Send(buf);
        }
    
      private:
        TcpSocket sock_;
        std::string ip_;
        uint16_t port_;
    
    };
    
    
    • dict_server.cc
    #include <unordered_map>
    #include "tcp_server.hpp"
    
    std::unordered_map<std::string, std::string> g_dict;
    
    void Translate(const std::string& req, std::string* resp) 
    {
      auto it = g_dict.find(req);
      if (it == g_dict.end()) 
      {
        *resp = "未找到";
        return;
      }
    
      *resp = it->second;
      return;
    }
    
    #if 0
    int main(int argc, char* argv[]) 
    {
      if (argc != 3) 
      {
        printf("Usage ./dict_server [ip] [port]\n");
        return 1;
      }
      
      // 1. 初始化词典
       g_dict.insert(std::make_pair("hello", "你好"));
       g_dict.insert(std::make_pair("world", "世界"));
       g_dict.insert(std::make_pair("bit", "贼NB"));
    
       // 2. 启动服务器
        TcpServer server(argv[1], atoi(argv[2]));
        server.Start(Translate);
    
        return 0;
       //
    }
    #endif
    
    int main() 
    {
      
      // 1. 初始化词典
       g_dict.insert(std::make_pair("hello", "你好"));
       g_dict.insert(std::make_pair("world", "世界"));
       g_dict.insert(std::make_pair("bit", "贼NB"));
    
       // 2. 启动服务器
        TcpServer server("0.0.0.0",9090);
        server.Start(Translate);
    
        return 0;
       //
    }
    
    
    • dict_client.cc
    #include "tcp_client.hpp"
    #include <iostream>
    
    int main(int argc, char* argv[]) 
    {
      if (argc != 3) 
      {
        printf("Usage ./dict_client [ip] [port]\n");
        return 1;
      }
    
      TcpClient client(argv[1], atoi(argv[2]));
      bool ret = client.Connect();
      if (!ret) 
      {
        return 1;
      }
    
      for (;;) 
      {
        std::cout << "请输入要查询的单词:" << std::endl;
        std::string word;
        std::cin >> word;
    
        client.Send(word);
        std::string result;
        client.Recv(&result);
        std::cout <<word<<" 意思是:"<< result << std::endl;
      }
    return 0;
    }
    
    

    在这里插入图片描述
    在这里插入图片描述

    展开全文
  • 网络编程套接字(一)

    千次阅读 2020-01-17 17:43:46
    网络编程套接字(一) 一、基础知识 1. 理解源IP地址和目的IP地址 在IP数据包头部中,有两个IP地址,分别叫源IP地址和目的IP地址。这两个很好理解,见名知义。 思考: 我们光有IP地址就可以完成通信了嘛? 想象一下发...
  • 网络编程套接字(Socket)

    千次阅读 2018-12-24 15:58:48
    网络编程套接字一.IP地址和端口号1.IP地址2.端口号2.1 什么是端口号2.2 端口号和进程ID2.3 源端口号和目的端口号二.初识TCP/UDP协议和网络字节序1.TCP(传输控制协议)2.UDP(用户数据报协议)3.网络字节序 一.IP地址和...
  • Linux网络编程 套接字

    2013-05-28 16:27:47
    Linux网络编程 套接字  一:概述  Socket 的英文原意就是“孔”或“插座”,现在,作为BSD UNIX 的进程通讯机制,取其后一种意义。日常生活中常见的插座,有的是信号插座,有的是电源插座,有的可以接受信号(或...
  • 在学习Linux系统编程的时候,进程间的通信方式包括——管道、消息队列、共享内存、信号量等方式。但是这些通信方式都村子一定的缺陷——都是在同一个机器上的进程间...Linux网络编程---套接字套接字(socket)是...
  • socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6以及UNIX Domain Socket,然而各种网络协议的地址格式不同。 IPv4和IPv6的地址格式定义在netinet/in.h中。 IPv4地址用sockaddr_...
  • 套接字有很多的选项,可以帮助我们定制化很多的功能。这里就来小结一些通用的套接字的选项。 getsockopt && setsockopt 函数 #include <sys/types.h> #include <sys/socket.h> int ...
  • IPV4 套接子结构 struct sockaddr_in{ ... //套接字长度,后来加入的参数,为了支持兼容和确定长度 sa_family_t sin_family; //协议类型 in_port_t sin_port; //端口号 16位 struct in_addr ...
  • 预备知识1.1 源IP地址和目的IP地址1.2 认识端口号1.3 理解 "端口号" 和 "进程ID"1.4 源端口号和目的端口号1.5 认识UDP协议1.6 认识TCP协议1.7 网络字节序2. socket编程接口2.1 socket 常见API2.1.1 创建2.1.2 绑定...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 15,284
精华内容 6,113
关键字:

网络编程套接字