本文将会简单介绍Linux下如何利用C库函数与系统调用编写一个完整的、初级可用的C-S模型。
一、基本模型:
1.1 首先服务器调用socket()函数建立一个套接字,然后bind()端口,开始listen()监听,此时,套接字变成了被动的套接字,用于侦听客户端的请求。然后accept(),开始阻塞监听客户端的请求。
1.2 客户端以服务端的参数为参数,用socket()建立套接字,然后connect()连接服务端。
1.3 服务端收到连接请求,accept()返回一个标识符,继续执行后面的指令。具体如图:
1.4 服务端创建套接字与绑定端口代码(打码处为点分十进制的IP字符串,注意,端口号用函数转换成网络传输的大端的Long类型,点分十进制IP转换成二进制。):
1.5 服务端监听与accept,并且循环读写代码:
while结束后close(conn) close(sockfd)即可。
1.6 客户端(打码处为服务端的IP与端口)
二、多进程
2.1
由于accept是阻塞的函数,如果用单进程进行,那么将会只能接受一个客户端的请求。而对于其他客户端的请求,则代码不会返回去重新执行accept();
要注意的是,其他客户端连接服务器时,即使服务器只接受一个客户端的业务请求,但是内核会帮我们完成TCP的三次握手连接,放入已连接的队列中等待服务器的accept去取走,不过,由于我们没有开启新的进程,所以,后来的这些连接没有机会被取走了。如图:
2.2
针对以上问题,可以将accept()放入while循环中,然后每一次接收到了连接,则创建一个子进程,然后子进程去执行读写操作,而父进程则继续监听。代码如下:
1 #include <unistd.h> 2 #include <sys/stat.h> 3 #include <sys/wait.h> 4 #include <sys/types.h> 5 #include <fcntl.h> 6 7 #include <stdlib.h> 8 #include <stdio.h> 9 #include <errno.h> 10 #include <string.h> 11 #include <signal.h> 12 13 #include <arpa/inet.h> 14 #include <sys/socket.h> 15 #include <netinet/in.h> 16 17 int main(void) 18 { 19 int sockfd; 20 // 创建一个Socket 21 sockfd = socket(AF_INET,SOCK_STREAM,0); 22 if(sockfd == -1){ 23 perror("error"); 24 exit(0); 25 } 26 27 28 /////////////////////////////////////////////////////////// 29 // struct sockaddr addr; // 这是一个通用结构,一般是用具体到,然后转型 30 struct sockaddr_in sockdata; 31 sockdata.sin_family = AF_INET; 32 sockdata.sin_port = htons(8001); 33 sockdata.sin_addr.s_addr = inet_addr(我是IP字符串); 34 35 int optval = 1; 36 if(setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&optval,sizeof(optval)) == -1) 37 { 38 perror("error"); 39 exit(0); 40 } 41 if(bind(sockfd,(struct sockaddr *)&sockdata,sizeof(sockdata)) < 0){ 42 perror("error"); 43 exit(0); 44 } 45 46 //////////////////////////////////////////////////////////// 47 if(listen(sockfd,5) == -1){ 48 perror("error"); 49 exit(0); 50 } 51 52 ////////////////////////////////////////////////////////// 53 struct sockaddr_in peeradr; 54 socklen_t peerlen = sizeof(peeradr); // 得有初始值 55 56 57 ///////////////////////////////////////////////////////// 58 char recvBuff[1024]={0}; 59 int conn = 0; 60 while(1){ 61 conn = accept(sockfd,(struct sockaddr *)&peeradr,&peerlen); 62 if(conn == -1){ 63 perror("error"); 64 exit(0); 65 } 66 // 每来一个链接fork一个进程。 67 pid_t pid; 68 pid = fork(); 69 70 if(pid == 0){ 71 int ret = 0; 72 close (sockfd); // 由于子进程复制来sockfd,所以关掉它。 不干涉父进程来,因为这是一个副本。由父进程继续监听 73 printf("收到的IP %s\n 客户端端口是:%d\n",inet_ntoa(peeradr.sin_addr),ntohs(peeradr.sin_port)); 74 while((ret = read(conn,recvBuff,sizeof(recvBuff))) && ret > 0){ 75 // 服务器收到打印数据,然后回发 76 fputs(recvBuff,stdout); 77 write(conn,recvBuff,ret); 78 } 79 exit(0); 80 } 81 else if(pid > 0){ 82 close(conn); // 父进程只管监听,不需要链接套接字!! 83 } 84 else{ 85 perror("error"); 86 close(conn); 87 close(sockfd); 88 exit(0); 89 } 90 } 91 close(conn); 92 close(sockfd); 93 return 0; 94 }
而客户端没有什么变化,代码如下:
1 #include <unistd.h> 2 #include <sys/stat.h> 3 #include <sys/wait.h> 4 #include <sys/types.h> 5 #include <fcntl.h> 6 7 #include <stdlib.h> 8 #include <stdio.h> 9 #include <errno.h> 10 #include <string.h> 11 #include <signal.h> 12 13 #include <arpa/inet.h> 14 #include <sys/socket.h> 15 #include <netinet/in.h> 16 17 int main(void) 18 { 19 int sockfd; 20 // 创建一个Socket 21 sockfd = socket(AF_INET,SOCK_STREAM,0); 22 if(sockfd == -1){ 23 perror("error"); 24 exit(0); 25 } 26 27 28 /////////////////////////////////////////////////////////// 29 // struct sockaddr addr; // 这是一个通用结构,一般是用具体到,然后转型 30 struct sockaddr_in sockdata; 31 sockdata.sin_family = AF_INET; 32 sockdata.sin_port = htons(8001); 33 sockdata.sin_addr.s_addr = inet_addr("我是IP字符串"); 34 if(connect(sockfd,(struct sockaddr *)&sockdata,sizeof(sockdata)) == -1){ 35 perror("error"); 36 exit(0); 37 } 38 39 char sendBuff[1024] = {0}; 40 char recvBuff[1024] = {0}; 41 42 while(fgets(sendBuff,sizeof(sendBuff),stdin) != NULL){ 43 // 将输入到数据送到服务端 44 write(sockfd,sendBuff,sizeof(sendBuff)); 45 // 从服务器读数据 46 read(sockfd,recvBuff,sizeof(recvBuff)); 47 48 // 显示在屏幕上 49 fputs(recvBuff,stdout); 50 // 清零 51 memset(recvBuff,0,sizeof(recvBuff)); 52 memset(sendBuff,0,sizeof(sendBuff)); 53 } 54 55 close(sockfd); 56 return 0; 57 }