网络编程之【TCP服务器】

TCP服务器

基于上一篇文章,我们利用socket编写了一个UDP服务器,链接如下:UDP服务器
今天我们来实现一个TCP服务器并且对这个TCP服务器进行优化。


TCP协议的特点

  • 有连接的
  • 面向字节流的
  • 可靠的传输
    -

TCP服务器需要用到的接口
TCP服务器和UDP服务器相同,都是利用SOCKET API接口来实现的,那么创建socket,和绑定bind和UDP是一摸一样的,但是因为我们上面讲到的TCP协议是有连接的,所以它不仅仅是创建和绑定就完成了,还需要连接的过程,因此就比UDP协议多出了下列几个接口:
1.listen()
这里写图片描述
listen这个函数用于把我们创建的套接字设置为监听套接字,用来接收连接请求,第一个参数sockfd是要设置为接听套接字的文件描述符,至于这里的第二个参数backlog,即表示等待队列长度,这个参数是什么意思呢?当我们的连接请求太多的时候,服务器就会把这些来不及服务的连接请求放入这个等待队列当中,因此这个队列长度我们一般不用设置太大,不然会造成客户盲等。
调用成功返回的是0,失败返回-1。此时套接字称之为监听套接字。
2.accpet
这里写图片描述
当我们创建好监听套接字之后,这个套接字就好像一个吆喝客人的人,一直帮我们处理连接请求,但是它只负责处理连接,并不负责处理具体的服务,而accept()这个函数就是解决这个问题的。它对于每一个连接请求,创建一个对应的socket来服务这个连接,就好像我们进饭馆吃饭的时候,每一桌有一个专门的服务人员服务我们一样。注意第一个参数,一定是我们创建的监听套接字。不难理解,这里的结构体sockaddr肯定就是客户啦。
3.connetc
这里写图片描述
完成了上面两部,一个服务器的基本框架以及算得上完成了,这时候就需要客户来连接请求了,因此connect就是用于客户端来进行连接的。这里的sockaddr肯定是服务器的地址啦。
4.read和write
在我们编写UDP服务器的时候,我们使用的是recvfrom和sendto来实现数据的交互,但是别忘了我们的socket本质上是一个文件描述符,那么当然可以像读写文件一样的对它进行数据的写入和读出啦。下面实现的TCP服务器就是使用read和write的。


代码实现
服务器端:

  1 #include<stdio.h>
  2 #include<sys/socket.h>
  3 #include<sys/types.h>
  4 #include<arpa/inet.h>
  5 #include<stdlib.h>
  6 #include<string.h>
  7 #include<string.h>
  8 void useerro(char*argv[])
  9 {  
 10    
 11     printf("[%s][ ip:][port]\n",argv[0]);
 12     exit(1);
 13 }  
 14 int main(int argc,char* argv[]  )
 15 {   
 16 if(argc!=3)
 17 {   
 18     useerro(argv);
 19 }  
 20 int sock=socket(AF_INET,SOCK_STREAM,0);
 21 if(sock<0)
 22 {   
 23    
 24     perror("socket");
 25     exit(2);
 26 }   
 27     
 28     struct sockaddr_in listen_socket;
 29 struct sockaddr_in server;
 30 server.sin_family=AF_INET;
 31 server.sin_port=htons(atoi(argv[2]));
 32 server.sin_addr.s_addr=inet_addr(argv[1]);
 33 if(bind(sock,(struct sockaddr*)&server,sizeof(server))<0)
 34 {   
 35     
 36     perror("bind");
 37     close(sock);
 38     exit(3);
 39 }   
 40 if(listen(sock,10)<0)
 41 {  
 42     
 43     perror("listen");
 44     close(sock);
 45     exit(4);
 46 }  
 47 printf("bind and listen successful,wait accept\n");
 48    
 49 while(1)
 50 {  
 51    
 52     socklen_t len=0;
 53     int listen_sock=accept(sock,(struct sockaddr*)&listen_socket,&len);
 54     if(listen_sock<0)
 55     {
 56 perror("accept");
 57 close(sock);
 58 exit(5);
 59     }
 60     char buf_ip[100];
 61     memset(buf_ip,'\0',sizeof(buf_ip));
 62     inet_ntop(AF_INET,&listen_socket.sin_addr,buf_ip,sizeof(buf_ip));
 63     printf("get connect,ip is:%s,port if:%d\n",buf_ip,ntohs(listen_socket.sin_port));
 64     while(1)
 65     {
 66    
 67         char buf[1024];
 68         memset(buf,'\0',sizeof(buf));
 69         read(listen_sock,buf,sizeof(buf));
 70         printf("client :#%s\n",buf);
 71         printf("server:$");
 72         
 73         memset(buf,'\0',sizeof(buf));
 74         fgets(buf,sizeof(buf),stdin);
 75         buf[strlen(buf)-1]='\0';
 76         write(listen_sock,buf,strlen(buf)+1);
 77         printf("please wait ...\n");
 78    
 79         }
 80 }  
 81 close(sock);
 82     return 0;

客户端:

  1 #include<stdio.h>
  2 #include<netinet/in.h>
  3 #include<sys/types.h>
  4 #include<sys/socket.h>
  5 #include<string.h>
  6 #include<arpa/inet.h>
  7 #include<stdlib.h>
  8 #define SERVER_PORT 8080 
  9 #define SERVER_IP "127.0.0.1"
 10 void useerro(char*argv[])
 11 {      
 12        
 13     printf("[%s][server ip:]\n",argv[0]);//客户端不需要绑定端口,由内核自动分配
 14     exit(1);
 15 }      
 16 int main(int argc,char *argv[])
 17 {      
 18     if(argc!=2)
 19     {
 20      
 21         useerro(argv);
 22     }  
 23     char buf[1024];
 24     memset(buf,'\0',sizeof(buf));
 25     int sock=socket(AF_INET,SOCK_STREAM,0);
 26     if(sock<0)
 27     { 
 28       
 29         perror("socket");
 30         exit(2);
 31       
 32     } 
 33     struct sockaddr_in server;
 34          bzero(&server,sizeof(server));
 35     server.sin_family=AF_INET;
 36     server.sin_port=htons(SERVER_PORT);
 37          inet_pton(AF_INET,SERVER_IP,&server.sin_addr);
 38       int ret=connect(sock,(struct sockaddr*)&server,sizeof(server));
 39       if(ret<0)
 40       {
 41       
 42           printf("connect failed...\n");
 43           exit(3);
 44       }
 45       printf("connect success\n");
 46 while(1)
 47 {     
 48     printf("client :#");
 49     fgets(buf,sizeof(buf),stdin);
 50     buf[strlen(buf)-1]='\0';
 51     write(sock,buf,sizeof(buf));
 52     if(strncasecmp(buf,"quit",4)==0)
 53     { 
 54 printf("quit\n");
 55 break;
 56       
 57     } 
 58     printf("please wait:#\n");
 59 read(sock,buf,sizeof(buf));
 60 printf("server :%s\n",buf);
 61 }     
 62 close(sock);
 63 return 0;
 64 }       

验证结果:
这里我们ip地址使用127.0.0.1 ,本地环回测试。
服务器端:
这里写图片描述
客户端:
这里写图片描述


但是我们写的这个服务器是单进程的,它一次只能处理一个连接请求,因此我们可以对它进行优化,实现多进程多线程的服务器,这样就可以处理多个连接请求。
多进程服务器:

  1 #include<stdio.h>
  2 #include<sys/socket.h>
  3 #include<sys/types.h>
  4 #include<arpa/inet.h>
  5 #include<stdlib.h>
  6 #include<string.h>
  7 #include<string.h>
  8 #include<unistd.h>
  9 void useerro(char*argv[])
 10 {   
 11    
 12     printf("[%s][ ip:][port]\n",argv[0]);
 13     exit(1);
 14 }   
 15 void ProcessRequest(int client_sock,struct sockaddr_in*client_addr)
 16 {  
 17    
 18     char buf_ip[100];
 19     memset(buf_ip,'\0',sizeof(buf_ip));
 20     inet_ntop(AF_INET,&client_addr->sin_addr,buf_ip,sizeof(buf_ip));
 21     printf("get connect,ip is:%s,port is:%d\n",buf_ip,ntohs(client_addr->sin_port));
 22         char buf[1024];
 23         memset(buf,'\0',sizeof(buf));
 24     while(1)
 25     {
 26     ssize_t read_size=read(client_sock,buf,sizeof(buf));
 27     if(read_size<0)
 28     {
 29    
 30         perror("read");
 31         continue;
 32     }
 33     else if(read_size==0)
 34     {
 35    
 36         printf("client:%s say goodbye\n",inet_ntoa(client_addr->sin_addr));
 37         close(client_sock);
 38         break;
 39     }
 40     else
 41     {
 42     buf[read_size]=0;
 43     printf("client[%s] say:#%s\n",inet_ntoa(client_addr->sin_addr),buf);
 44     write(client_sock,buf,strlen(buf));
 45     }
 46    
 47     }
 48 }   
 49 void createworker(int client_sock,struct sockaddr_in*client_addr)
 50 {  
 51         pid_t pid;
 52     pid=fork();
 53    if(pid==0)
 54    {//child
 55 if(fork()==0)
 56 {  
 57     //grandchild
 58 ProcessRequest(client_sock,client_addr);
 59 }  
 60 else
 61 {  
 62     exit(0);
 63 }  
 64    
 65    }
 66    else if(pid>0)
 67    {//father
 68        
 69        waitpid(pid,NULL,0);
 70        close(client_sock);
 71    }
 72    else
 73    {
 74     
 75        perror("fork");
 76        return ;
 77    }
 78         
 79    
 80 }  
 81 int main(int argc,char* argv[]  )
 82 {   
 83 if(argc!=3)
 84 {   
 85     useerro(argv);
 86 }  
 87 int sock=socket(AF_INET,SOCK_STREAM,0);
 88 if(sock<0)
 89 {   
 90    
 91     perror("socket");
 92     exit(2);
 93 }  
 94     
 95 struct sockaddr_in server;
 96 server.sin_family=AF_INET;
 97 server.sin_port=htons(atoi(argv[2]));
 98 server.sin_addr.s_addr=inet_addr(argv[1]);
 99 if(bind(sock,(struct sockaddr*)&server,sizeof(server))<0)
100 {  
101    
102     perror("bind");
103     close(sock);
104     exit(3);
105 }  
106 if(listen(sock,10)<0)
107 {  
108    
109     perror("listen");
110     close(sock);
111     exit(4);
112 }  
113 printf("bind and listen successful,wait accept\n");
114    
115 while(1)
116 {  
117    
118     struct sockaddr_in client_addr;
119     socklen_t len=0;
120     int client_sock=accept(sock,(struct sockaddr*)&client_addr,&len);
121     if(client_sock<0)
122     {
123 perror("accept");
124 continue;
125     }
126     createworker(client_sock,&client_addr);
127    
128         
129 }  
130 close(sock);
131     return 5;
132 }   

这里要说明一下,其中有一个重要的点,就是两次fork的原因。我们的父进程在创建完子进程之后,必须要wait或者waitpid来等待子进程的退出,否则可能造成僵尸进程,但是我们希望的是,当来一个连接请求的时候,就fork出一个进程来处理它,紧接着又去处理别的连接请求,不必关心创建的进程的服务情况,要解决这个问题,有两个方法,一是在学习信号的时候,我们知道子进程退出会给父进程发送一个17号的SIGCHLD信号,当我们不关心子进程的退出情况时,可以把这个信号捕捉为忽略。
第二种方式就是我们现在实现的,通过两次fork,第一次fork完成,父进程等待子进程,子进程再次fork出孙子进程,此时子进程退出,那么父进程等待结束,继续处理连接,而孙子进程由于父进程退出变成了一个孤儿进程,因此需要被1号进程领养,进程结束由系统自动回收资源,不必关心。
但是多进程服务器也有缺点:每一次连接都要创建一个进程,而创建进程就要分配PCB,和虚拟地址空间,简历虚拟地址和物理地址的页表映射,当连接请求多的时候,服务器压力很大,因此我们想到了多线程。


多线程版本:

  1 #include<stdio.h>
  2 #include<sys/socket.h>
  3 #include<sys/types.h>
  4 #include<arpa/inet.h>
  5 #include<stdlib.h>
  6 #include<string.h>
  7 #include<string.h>
  8 #include<pthread.h>
  9 typedef struct Arg
 10 {  
 11     int fd;
 12     struct sockaddr_in addr;
 13 }Arg;
 14 void useerro(char*argv[])
 15 {  
 16    
 17     printf("[%s][ ip:][port]\n",argv[0]);
 18     exit(1);
 19 }  
 20 void ProcessRequest(int client_sock,struct sockaddr_in* client_addr)
 21 {   
 22     char buf[1024]={0};
 23     while(1)
 24     {
 25 ssize_t read_size=read(client_sock,buf,sizeof(buf));
 26 if(read_size<0)
 27 {   
 28 perror("read");
 29 continue;
 30 }  
 31 else if(read_size==0)
 32 {  
 33 printf("client[%s]:say good bye\n",inet_ntoa(client_addr->sin_addr));
 34 close(client_sock);
 35 break;
 36    
 37 }  
 38 buf[read_size]=0;
 39 printf("client[%s]say:#%s\n",inet_ntoa(client_addr->sin_addr),buf);
 40 write(client_sock,buf,strlen(buf));
 41     }
 42 return ;
 43    
 44 }  
 45 void*createworker(void *ptr)
 46 {   
 47   Arg*arg=(Arg*)ptr;
 48   ProcessRequest(arg->fd,&arg->addr);
 49   free(arg);
 50   arg=NULL;
 51   return NULL;
 52    
 53 }  
 54 int main(int argc,char* argv[]  )
 55 {   
 56 if(argc!=3)
 57 {   
 58     useerro(argv);
 59 }  
 60 int sock=socket(AF_INET,SOCK_STREAM,0);
 61 if(sock<0)
 62 {   
 63     
 64     perror("socket");
 65     exit(2);
 66 }   
 67     
 68     struct sockaddr_in listen_socket;
 69 struct sockaddr_in server;
 70 server.sin_family=AF_INET;
 71 server.sin_port=htons(atoi(argv[2]));
 72 server.sin_addr.s_addr=inet_addr(argv[1]);
 73 if(bind(sock,(struct sockaddr*)&server,sizeof(server))<0)
 74 {  
 75    
 76     perror("bind");
 77     close(sock);
 78     exit(3);
 79 }  
 80 if(listen(sock,10)<0)
 81 {  
 82    
 83     perror("listen");
 84     close(sock);
 85     exit(4);
 86 }  
 87 printf("bind and listen successful,wait accept\n");
 88    
 89 while(1)
 90 {  
 91  struct sockaddr_in client_addr;
 92     socklen_t len=0;
 93     int client_sock=accept(sock,(struct sockaddr*)&client_addr,&len);
 94     if(client_sock<0)
 95     {
 96 perror("accept");
 97 continue;
 98     }
 99     pthread_t t1;
100     Arg* arg=(Arg*)malloc(sizeof(Arg));
101     arg->fd=client_sock;
102     arg->addr=client_addr;
103     pthread_create(&t1,NULL,createworker,(void*)arg);
104     pthread_detach(t1);
105 }  
106 close(sock);
107     return 0;
108 } 

同样为了解决线程需要等待的问题,我们把线程分离掉,多线程也有缺点:因为线程之间大部分资源是共享的,因此必须保证同步和互斥,线程安全必须要考虑。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值