网络基础1------------------TCP套接字编程(单连接版本、多进程版本、多线程版本)

单连接版本

掌握socket常见API(重要掌握)
1、创建socket文件描述符(TCP/UDP,客户端+服务器)
int socket(int domain,int type,int protocol);
//参数1是指定协议类型(ipv4或者ipv6);参数2是指传输数据方式(面向数据报还是面向字节流);参数3默认为02、绑定端口号(TCP/UDP,服务器)
int bind(int socket,const struct sockaddr* address,socklen_t address_len);
//参数1socket文件描述符,参数2是绑定到本机的端口号和地址,参数3是参数2结构体的大小
3、监听socket(TCP,服务器)
只有监听成功才能允许客户端连接。
int listen(int socket,int backlog);//backlog相当于等待的服务器,只有多一点才有可能有空闲的处理请求
4、接收请求(TCP,服务器)
int accept(int socket,struct sockaddr* address,socklen_t* address_len);
5、建立连接(TCP,客户端)
int connect(int sockfd,const struct sockaddr* addr,socklen_t addrlen);

这里写图片描述

server.c:
  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<sys/socket.h>
  4 #include<string.h>
  5 #include<unistd.h>                                                                                                               
  6 #include<netinet/in.h>
  7 #include<arpa/inet.h>
  8 
  9 
 10  typedef struct sockaddr sockaddr;
 11  typedef struct sockaddr_in sockaddr_in;
 12 
 13 void ProcessConnect(int new_sock){
 14   //完成本次连接的处理,循环的处理客户端的请求。
 15     while(1){
 16         char buf[1024]={0};
 17         ssize_t read_size=read(new_sock,buf,sizeof(buf)-1);
 18         if(read_size<0){
 19     continue;
 20         }
 21         if(read_size==0){
 22             //TCP中如果read的返回值为0,说明对端关闭了连接,因此再去读就只能读到0个字节
 23             printf("[client %d]disconnect\n",new_sock);
 24             close(new_sock);
 25             return ;
 26         }
 27         buf[read_size]='\0';
 28         printf("client[%d] say:%s\n",new_sock,buf);
 29         //将相应结果写回客户端
 30         write(new_sock,buf,strlen(buf));                                                                                         
 31     }
 32 }
 33 
 34 
 35 int main(int argc,char* argv[]){
 36     if(argc!=3){
 37         printf("Usage:[ip]");
 38         return -1;
 39     }
 40     //创建socket文件描述符
 41     int sockfd=socket(AF_INET,SOCK_STREAM,0);
 42     if(sockfd<0){
 43         perror("socket\n");
 44         return -2;
 45     }
 46     //绑定端口号
 47     sockaddr_in server_addr;                                                                                                     
 48     server_addr.sin_family=AF_INET;
 49     server_addr.sin_addr.s_addr=inet_addr(argv[1]);//inet_addr将点分十进制的字符串转成32位整数
 50     server_addr.sin_port=htons(atoi(argv[2]));
 51     if(bind(sockfd,(struct sockaddr*)&server_addr,sizeof(server_addr))<0){
 52         perror("bind\n");
53         return 3;
 54     }
 55     //使用listen允许服务器被客户端连接
 56     if(listen(sockfd,5)<0){
 57         perror("listen");
 58         return 1;
 59     }
 60     printf("bind and listen success,wait......\n");
 61     //开始事件循环(TCP建立连接是在内核中建立的)
 62     while(1){
 63         sockaddr_in client;                                                                                                      
 64         socklen_t len=sizeof(client);
 65         int req_sock=accept(sockfd,(sockaddr*)&client,&len);//将内核中建立好的连接放到用户空间(将小板凳上的人请到屋中)
 66         if(req_sock<0){
 67             perror("accept\n");
       //不能因为单个客户端的失败挂掉整个服务器
 68             continue;
 69          }
 70         ProcessConnect(req_sock);
 71     }
 72         return 0;
 73 }           
client.c
1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<sys/types.h>
  5 #include<netinet/in.h>
  6 #include<arpa/inet.h>                                                                                                            
  7 #include<string.h>
  8 #include<sys/socket.h>
  9 
 10 
 11 int main(int argc,char* argv[]){
 12     if(argc!=3){
 13         printf("Usage:[ip][port]\n");
 14         return 1;
 15     }
 16 
 17     int sock=socket(AF_INET,SOCK_STREAM,0);
 18     if(sock<0){
 19         perror("sock\n");
 20         return 1;
 21     }
 22     //指定客户端要连接的服务器ip和端口号是多少?
 23     struct sockaddr_in server;
 24     server.sin_family=AF_INET;
 25     server.sin_addr.s_addr=inet_addr(argv[1]);
 26     server.sin_port=htons(atoi(argv[2]));
 27     int ret=connect(sock,(struct sockaddr*)&server,sizeof(server));
 28     if(ret<0){
 29         printf("connect failed\n");
 30         return 1;                                                                                                                
 31     }
 32     //事件循环
 33     while(1){
 34         char buf[1024]={0};
 35         printf("client say:");
36         ssize_t read_size=read(0,buf,sizeof(buf)-1);
 37         if(read_size<0){
 38             perror("read fail\n");
 39             return 1;
 40         }                                                                                                                        
 41         if(read_size==0){
 42             printf("read done\n");
 43             return 0;
 44         }
 45         buf[read_size]='\0';
 46         write(sock,buf,strlen(buf));
 47         printf("wait response..\n");
 48         char buf_resp[1024]={0};
 49         read_size=read(sock,buf_resp,sizeof(buf_resp)-1);
 50         if(read_size<0){
 51             perror("read fail\n");
 52             return 1;
  53         }
 54         if(read_size==0){
 55             printf("server close connect\n");
 56             return 0;
 57         }
 58         buf_resp[read_size]='\0';
 59         printf("server say:%s\n",buf_resp);
 60     }                                                                                                                            
 61     return 0;
 62 }          

由图中可以看出服务器处于监听状态。
这里写图片描述
运行程序可以实现服务器和客户端数据交互,而且当客户端退出时,服务器断开连接。
测试多个链接情况时,第二个客户端不能正常和服务器进行通信,原因在于我们设计accept一个请求之后就不断进入循环read(阻塞式),第一个客户端没有断开就不能调用到下一个accept,所以第二个连接一直在内核上(说明连接可以建立成功),没有在用户空间里所以就无法接受新请求。
所以就想到使用多进程版本使得accept之后引入多个执行流同时完成两类操作,并行执行互不干扰。(采用多进程版本和多线程版本)

多进程版本

具体思路是:创建进程让子进程再创建孙子进程处理请求,子进程退就能执行下一次accept。而父进程就负责防止僵尸进程的产生即可。
实现代码:

#include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>                                                                                                               
  4 #include<sys/socket.h>
  5 #include<arpa/inet.h>
  6 #include<netinet/in.h>
  7 #include<sys/wait.h>
  8 #include<string.h>
  9 #include<signal.h>
 10 
 11 
 12 //进程中有一个致命的错误,创建子进程负责与客户端进行读写交互,fork同时会拷贝文件描述符表
 13 //描述符表的生命周期随进程所以子进程后面调用exit(0);就会自动关闭sock文件描述符表,但是父进程不会,所以要手动关闭,以免泄漏。
 14 void ProcessConnect(int newfd){
 15     pid_t id=fork();
 16     if(id<0){
 17         perror("fork\n");
}
 20     else if(id>0){
 21         //father
 22         //进程中有一个致命的错误,创建子进程负责与客户端进行读写交互,fork同时会拷贝文件描述符表\
 23         // 描述符表的生命周期随进程所以子进程后面调用exit(0);就会自动关闭sock文件描述符表,\
 24         // 但是父进程不会,所以要手动关闭,以免泄漏。
 25         close(newfd);
 26         return ;//让父进程负责不断调用accept。        
 27     }
 28     //子进程只负责和客户端进行交互。如果read返回0,那么说明客户端关闭了文件描述符,\
 29     //就让子进程直接退出,因为返回子进程就会执行accept(有父进程负责)
 30     while(1){                                                                                                                    
 31         char buf[1024]={0};
 32         ssize_t read_size=read(newfd,buf,sizeof(buf)-1);
 33         if(read_size<0){
 34             continue;
 35         }
if(read_size==0){
 37             //说明对端关闭连接,那么子进程就直接退出即可
 38             printf("[client %d] disconnect\n",newfd);
 39             close(newfd);
 40             exit(0);//不能使用return,这样的话子进程就会去执行accept,这是由父进程负责的。
 41         }
 42         buf[read_size]='\0';
 43         printf("client say:%s\n",buf);
 44         //服务器作出响应
 45         write(newfd,buf,strlen(buf));                                                                                            
 46     }
 47 }

此时就可以多个客户端同时与服务器进行数据交互,但是创建进程虚拟地址空间需要各自独立一份故开销大,所以下面介绍多线程版本

多线程版本

#include<stdio.h>
    2 #include<stdlib.h>
    3 #include<unistd.h>                                                                                                             
    4 #include<sys/socket.h>
    5 #include<arpa/inet.h>
    6 #include<netinet/in.h>
    7 #include<sys/wait.h>
    8 #include<string.h>
    9 #include<pthread.h>
   10 
   11 void* ThreadEntry(void* arg){
   12     int64_t newfd=(int64_t)arg;
    13     while(1){
   14         char buf[1024]={0};
   15         ssize_t read_size=read(newfd,buf,sizeof(buf)-1);
   16         if(read_size<0){
   17             continue;                                                                                                          
   18         }
   19         if(read_size==0){
   20 
   21             printf("[client %ld] disconnect\n",newfd);
   22             close(newfd);
   23             return NULL;
   24         }
   25         buf[read_size]='\0';
   26         printf("client say:%s\n",buf);
   27         //服务器作出响应
   28         write(newfd,buf,strlen(buf));                                                                                          
   29     }
   30     return NULL;
   31 }
   32 void ProcessConnect(int64_t  newsock){
   33   pthread_t tid;
   34   pthread_create(&tid,NULL,ThreadEntry,(void*)newsock);
   35   //这里之所以可以强转是因为void* 有8个字节。超过int,但是用int64_t来表示解决警告。
   36   pthread_detach(tid);
   37   //为了保证线程能够回收而且可以很快的返回使用线程分离
   38 }                                                                                                                              
   39 
   40 
   41 
   42 int main(int argc,char* argv[]){
   43     if(argc!=3){
   44         perror("Usage[ip][port]\n");
 45         return 1;
   46     }
   47     int sockfd=socket(AF_INET,SOCK_STREAM,0);
   48     if(sockfd<0){                                                                                                              
   49         perror("socket\n");
   50         return 2;
   51     }
   52     struct sockaddr_in addr;
   53     addr.sin_family=AF_INET;
   54     addr.sin_addr.s_addr=inet_addr(argv[1]);
   55     addr.sin_port=htons(atoi(argv[2]));
   56    int ret=bind(sockfd,(struct sockaddr*)&addr,sizeof(addr));
   57    if(ret<0){
   58        perror("bind\n");
   59        return 1;                                                                                                               
   60    }
   61    if(listen(sockfd,5)<0){
   62        perror("listen\n");
   63        return 2;
   64    }
   65 //客户端和服务器已经连接好,分流处理accept和处理请求。
   66    while(1){
   67        struct sockaddr_in client;
   68        socklen_t len=sizeof(client);
   69        int newfd=accept(sockfd,(struct sockaddr*)&client,&len);
   70        if(newfd<0){                                                                                                            
   71            perror("accept\n");
   72            continue;
   73        }
   74        printf("[client %d] connect\n",newfd);
   75        ProcessConnect(newfd);
   76          }
   77    return 0;
   78 }    

比较多进程版本和多线程版本,多个客户端连接服务器,多进程版本是让依次退出了在连接上处理请求,所以socket文件描述符都是一样的,但是多线程版本是递增的,原因就在于多线程共用一份文件描述符表。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值