基于TCP协议下的socket编程

时间:2022-08-13 22:42:32

socket:

  TCP/IP协议中一个端口号和一个IP地址绑定在一起就生成一个socket就表示了网络中唯一的一个进程,它是全双工的工作方式。


基于TCP的socket编程

函数的使用:

1、socket()

        #include <sys/types.h>          /* See NOTES */       #include <sys/socket.h>

       int socket(int domain, int type, int protocol);

socket它用于创建一个套接字,返回值是一个文件描述符。

domin表示该套接字网络层所用的协议,因为我们用的是iPv4协议,所以这里填AF_INET。

type表示该套接字传输层使用什么模式传输,TCP是字节流的,所以这里填SOCK_STREAM。

protocol填写为0,系统会根据你之前的参数把它设置为相应字段,比如我们参数设置为AF_INET,SOCK_STREAM,0,后面的0会自动设置为IPPROTO_TCP。

2、bind()

  #include <sys/types.h>          /* See NOTES */       #include <sys/socket.h>       int bind(int sockfd, const struct sockaddr *addr,                socklen_t addrlen);

sockfd就是套接字

struct sockaddr:首先需要认识下面两个结构体struct sockaddr_in 和struct sockaddr_un。

基于TCP协议下的socket编程

通常情况下习惯于用sockaddr_in结构体来填充ip和端口号字段然后强转为struct sockaddr。

struct sockaddr_in { short sin_family;/*Address family一般来说AF_INET(地址族)PF_INET(协议族)*/ unsigned short sin_port;/*Port number(必须要采用网络数据格式,普通数字可以用htons()函数转换成网络数据格式的数字)*/ struct in_addr sin_addr; 使用inet_addr(),将字符串ip转化为网络数据的格式。 unsigned char sin_zero[8];/*Same size as struct sockaddr没有实际意义,只是为了 跟SOCKADDR结构在内存中对齐*/ };

3、网络字节序:

一台主机有大小端模式而在网络中传输数据的时候需要遵循一定的规则,即发送端从地地址开始发送,接收端从低地址开始接受,如果两台主机的大小端模式不一样,那么就会导致数据的错乱,tcp/ip协议中规定网络数据流应该采用大段的字节序,即高地址高位。为了方便就有了一系列转化的函数方便使用。

      #include <arpa/inet.h>       uint32_t htonl(uint32_t hostlong);       uint16_t htons(uint16_t hostshort);       uint32_t ntohl(uint32_t netlong);       uint16_t ntohs(uint16_t netshort);              //上面这些函数用于将端口号转化为大端模式       //下面的多用于将ip地址字符串转化为大端模式               #include <sys/socket.h>       #include <netinet/in.h>       #include <arpa/inet.h>       int inet_aton(const char *cp, struct in_addr *inp);       in_addr_t inet_addr(const char *cp);       in_addr_t inet_network(const char *cp);       char *inet_ntoa(struct in_addr in);       struct in_addr inet_makeaddr(int net, int host);       in_addr_t inet_lnaof(struct in_addr in);       in_addr_t inet_netof(struct in_addr in);

4、listen()

   #include <sys/types.h>          /* See NOTES */       #include <sys/socket.h>       int listen(int sockfd, int backlog);

listen的作用主要是用来将当前已经绑定的套接字设置为监听状态,保持一个LISTEN的状态

backlog的作用是用来设置当前最多能允许多少远端套接字在监听队列中排队。

5、accept()

  #include <sys/types.h>          /* See NOTES */       #include <sys/socket.h>       int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

accept的作用是将监听到的远端套接字accept到然后返回一个新的文件操作符,可以想象,如果不停的listen到则会有很多的文件操作符返回,再用一些方法便可以实现一对多的链接了,回到前面一个博客的问题,这时候如果大量远端套接字被accept到,然后服务器主动断开链接,大量的TIME_WAIT状态就是在这个时候产生的,(解决方法见上一篇博客)。

函数中的addr和addrlen即时输入型参数也是输出型参数,因为我们要获取远端的套接字信息

6、connect()

       #include <sys/types.h>          /* See NOTES */       #include <sys/socket.h>       int connect(int sockfd, const struct sockaddr *addr,                   socklen_t addrlen);

很显然这是一个客户端需要的一个函数,它用来链接远端的服务器。


下面是实现的代码:

分了三种形式来实现,一对一,一对多(多进程)和一对多(多线程)。



服务器端:

  1 #include<iostream>  2 #include<sys/socket.h>  3 #include<sys/types.h>  4 #include<arpa/inet.h>  5 #include<string.h>  6 #include<string>  7 #include<errno.h>  8 #include<stdlib.h>  9 #include<stdio.h> 10 #include<sys/wait.h> 11 #include<signal.h> 12 #include<pthread.h> 13  14 using namespace std; 15  16 //下面是在多进程下注册的一个信号处理函数,能使得父进程不用     //再阻塞式或者轮询式的等待子进程的退出,当有子进程退出时会     //发送SIGCHLD信号我们捕捉这个信号后对其回收。 17 void hander(int sign) 18 { 19     int status=0; 20     while(waitpid(-1,&status,WNOHANG)>0) 21     { 22         int cur1=(status>>8)&0xff;//取低八位的信号 23         int cur2=status&0xff;//取高八位的退出码 24         cout<<"a child leave.."<<"signal::"<<cur2<<"code::"<<cur1<<endl; 25     } 26 } 27  28 int Listen(string &ip,int port) 29 { 30     int sock=socket(AF_INET,SOCK_STREAM,0);//创建一个套接字 31     cout<<sock<<endl; 32     struct sockaddr_in server; 33     server.sin_family=AF_INET; 34     server.sin_port=htons(port); 35     server.sin_addr.s_addr=inet_addr(ip.c_str()); 36     int bindret=bind(sock,(struct sockaddr*)&server,sizeof(server));                //绑定到端口上,否则操作系统会分配一个随机端口。 37     if(bindret<0) 38     { 39         cout<<strerror(errno)<<endl; 40     } 41     int listen_sock=listen(sock,10);                 //设置监听状态 42     if(listen_sock<0) 43     { 44         cout<<strerror(errno)<<endl; 45         exit(2); 46     } 47     return sock; 48 } 49  50 void* output(void*output_sock)         //一个读端的线程,设置这个线程的目的是为了         //让主线程不停地accept而不停地使用这个线程         //来读数据 51 { 52     int sock=(int)output_sock; 53     char buf[1024]; 54     while(1) 55     { 56         ssize_t _size=read(sock,buf,sizeof(buf)-1); 57         if(_size>0){ 58                 buf[_size]='\0'; 59         }else if(_size<0){ 60                 cout<<strerror(errno)<<endl; 61         }else{ 62             cout<<"leave..."<<endl; 63             break; 64         } 65                 cout<<"client ::"<<buf; 66                 fflush(stdout); 67     } 68 } 69  70 int main(int argc,char *argv[]) 71 { 72     if(argc!=3) //命令行参数格式设置为 ./server ip port的格式 73     { 74         cout<<"please output like this"<<"argv[0]" <<"ip"<<"port"; 75         exit(1); 76     } 77     string ip(argv[1]); 78     int port = atoi(argv[2]); 79     int listen_sock=Listen(ip,port); 80     struct sockaddr_in client; //这个client的作用是为了获取远端的套接字信息 81     socklen_t len=sizeof(client); 82     while(1) 83     { 84        int output_sock=accept(listen_sock,(struct sockaddr*)&client,&len);                    //从监听队列中获取一个远端套接字信息,并新生成一个文件描述符。 85        if(output_sock<0) 86        { 87            continue; 88        } 89        char buf[1024]; 90        cout<<"get connect..."<<"ip::"<<inet_ntoa(client.sin_addr)<<"port\ 91                ::"<<ntohs(client.sin_port)<<endl; 92  93    #ifdef _TEST1_ //条件编译(在Makefile中gcc -o $@ $^ 后面加上-D_TEST1_即可完                             //       成条件编译)                           //TEST1完成的是一对一。这种模式显然有很大的缺点。只能满足一个                            //用户的服务器显然没有什么用处 94         //one connect 95         cout<<"TEST1"<<endl; 96         while(1) 97         { 98             memset(buf,'\0',sizeof(buf)); 99             ssize_t _size=read(output_sock,buf,sizeof(buf)-1); 100             if(_size>0) 101             { 102                 buf[_size]='\0'; 103             }else if(_size==0){ 104            cout<<"leave..."<<"ip::"<<inet_ntoa(client.sin_addrr)<<endl; 105                     break; 106             }else{ 107  108                 cout<<strerror(errno)<<endl; 109             } 110             cout<<"client::"<<buf; 111         } 112   #elif _TEST2_                  //TEST2实现的是一个多进程的一对多的服务器,虽然解决了多人的链接问题,                    //但是进程所占用的资源是很大的。         113       // fork 114       cout<<"TEST2"<<endl; 115       pid_t id=fork() 116       if(id==0){ 117           close(listen_sock);                           //子进程不需要listen_sock。只需要读数据就行了。 118           while(1) 119           { 120               ssize _size=read(output_sock,buf,sizeof(buf)-1); 121               if(_size>0) 122               { 123                  buf[_size]='\0'; 124               }else if(_size ==0){ 125                 cout<<"leave..."<<"ip::"<<inet_ntoa(client.sin_addr)<<end 126                     break;  127                 }else{ 128                     cout<<strerror(errno)<<endl; 129                 } 130             } 131             close(output_sock); 132         }else if(id<0){ 133             cout<<strerror(errno)<<endl; 134         }else{                              //父进程只需要关心listen_sock; 135             close(output_sock); 136             signal(SIGCHLD,hander); 137         } 138     #elif _TEST3_                      //实现的是一个多线程一对多的服务器,同样线程也有开销。 139         //pthread 140         cout<<"TEST3"<<endl; 141         pthread_t id; 142         pthread_create(&id,NULL,output,(void*)output_sock); 143         pthread_detach(id); 144     #else 145     cout<<"default"<<endl; 146     #endif 147 } 148     return 0; 149 }

(几处地方复制过来缩进有点问题,注释时复制过来后来添加的)。


客户端

客户端需要一个套接字,因为服务器需要知道你的信息,但他不需要绑定,它只关心链接到服务器端即可。

  1 #include<iostream>  2 #include<sys/socket.h>  3 #include<sys/types.h>  4 #include<arpa/inet.h>  5 #include<string.h>  6 #include<string>  7 #include<errno.h>  8 #include<stdlib.h>  9 #include<sys/wait.h> 10 #include<signal.h> 11 #include<pthread.h> 12 using namespace std; 13  14 int main(int argc,char*argv[]) 15 { 16     if(argc!=3) 17     { 18         cout<<"please input::"<<"ip[]"<<"port[]"<<endl; 19     }    20     int port=atoi(argv[2]); 21     const string server_ip=argv[1]; 22     int sock=socket(AF_INET,SOCK_STREAM,0); 23     if(sock==0) 24     { 25         cout<<strerror(errno)<<endl; 26         exit(1); 27     }    28     struct sockaddr_in client; 29     client.sin_family=AF_INET; 30     client.sin_port=htons(port); 31     client.sin_addr.s_addr=inet_addr(server_ip.c_str()); 32     int consock=connect(sock,(struct sockaddr*)&client,sizeof(sockaddr_in));         //链接到目的套接字,并把自己的套接字信息发送给对方 33     if(consock<0) 34     { 35         cout<<strerror(errno)<<endl; 36     } 37     string msg; 38     while(1) 39     { 40         cout<<"please write msg"<<endl; 41         cin>>msg; 42         write(sock,msg.c_str(),msg.size()); 43     } 44     return 0; 45 } 46


总结:虽然实现了一个一对多的服务器,但是弊端是很容易发现的,首先,不管是多线程还是多进程的服务器,它的资源消耗是很大的,再说说多线程和多进程的缺点,当连接数目非常大的时候,cpu的调度也会变的很麻烦,虽然说cpu的执行速度非常快,但当成千上万个进程或者线程在运行的时候它也是会吃不消的。

本文出自 “痕迹” 博客,请务必保留此出处http://wpfbcr.blog.51cto.com/10696766/1775986