网络中进程通信-----socket

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了网络中进程通信-----socket相关的知识,希望对你有一定的参考价值。

一、什么是socket?

socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。我的理解就是Socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)。

 (1)socket()函数

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

socket函数对应于普通文件的打开操作。普通文件的打开操作返回一个文件描述字,而socket()用于创建一个socket描述符(socket descriptor),它唯一标识一个socket。这个socket描述字跟文件描述字一样,后续的操作都有用到它,把它作为参数,通过它来进行一些读写操作。

正如可以给fopen的传入不同参数值,以打开不同的文件。创建socket的时候,也可以指定不同的参数创建不同的socket描述符,socket函数的三个参数分别为:

  • domain:即协议域,又称为协议族(family)。常用的协议族有,AF_INETAF_INET6AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。

  • type:指定socket类型。常用的socket类型有,SOCK_STREAMSOCK_DGRAMSOCK_RAWSOCK_PACKETSOCK_SEQPACKET等等(socket的类型有哪些?)。

  • protocol:故名思意,就是指定协议。常用的协议有,IPPROTO_TCPIPPTOTO_UDPIPPROTO_SCTPIPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。

        当我们调用socket创建一个socket时,返回的socket描述字它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()listen()时系统会自动随机分配一个端口。


(2)bind()函数

bind()函数把一个地址族中的特定地址赋给socket。例如对应AF_INETAF_INET6就是把一个ipv4或ipv6地址和端口号组合赋给socket。

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);


  sockfd:socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字

     addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。

      如ipv4对应的是: 

struct sockaddr_in {
    sa_family_t    sin_family; /* address family: AF_INET */
    in_port_t      sin_port;   /* port in network byte order */
    struct in_addr sin_addr;   /* internet address */};/* Internet address. */struct in_addr {
    uint32_t       s_addr;     /* address in network byte order */};

  addrlen:地址长度

通常服务器在启动的时候都会绑定一个众所周知的地址(如ip地址+端口号),用于提供服务,客户就可以通过它来接连服务器;而客户端就不用指定,有系统自动分配一个端口号和自身的ip地址组合。这就是为什么通常服务器端在listen之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。

注意:网络字节序与主机字节序

(3)listen()、connect()函数

如果作为一个服务器,在调用socket()bind()之后就会调用listen()来监听这个socket,如果客户端这时调用connect()发出连接请求,服务器端就会接收到这个请求。

int listen(int sockfd, int backlog);
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)

listen 函数:sockfd为要监听的socket的描述字,backlog:socket连接的最大个数。

listen函数监听连接,connec函数为客户端用来与服务器创建连接。


(4)accept()函数

TCP服务器端依次调用socket()bind()listen()之后,就会监听指定的socket地址了。TCP客户端依次调用socket()connect()之后就想TCP服务器发送了一个连接请求。TCP服务器监听到这个请求之后,就会调用accept()函数取接收请求,这样连接就建立好了。之后就可以开始网络I/O操作了,即类同于普通文件的读写I/O操作。

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

accept函数的第一个参数为服务器的socket描述字,第二个参数为指向struct sockaddr *的指针,用于返回客户端的协议地址,第三个参数为协议地址的长度。如果accpet成功,那么其返回值是由内核自动生成的一个全新的描述字,代表与返回客户的TCP连接。

注意:accept的第一个参数为服务器的socket描述字,是服务器开始调用socket()函数生成的,称为监听socket描述字;而accept函数返回的是已连接的socket描述字。一个服务器通常通常仅仅只创建一个监听socket描述字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建了一个已连接socket描述字,当服务器完成了对某个客户的服务,相应的已连接socket描述字就被关闭。

socket中TCP三次握手:

技术分享

客户端connect主动发送连接,向服务器发送SYN J包,然后阻塞等待服务器监听连接请求,并处理连接请求。服务器监听到连接请求并accept,这时进入accept阻塞状态,向客户端发送SYN K包,并确认请求ACK J+1;客户端收到服务器的确认信息后,然后connect返回确认,ACK K+1;服务器收到 ACK k+1,accept返回,三次握手连接成功。

socket中TCP四次挥手:

技术分享



代码:

tcp_server.c

  1 #include<stdio.h>
  2 #include <stdlib.h>
  3 #include<sys/types.h>
  4 #include<sys/socket.h>
  5 #include<arpa/inet.h>
  6 #include<netinet/in.h>
  7 #include<errno.h>
  8 #include<string.h>
  9 #include<pthread.h>
 10 
 11 const int back_log=5;
 41 int start_up(char* ip,int port)
 42 {
 43     //sock
 44   int sock=socket(AF_INET,SOCK_STREAM,0);
 45   if(sock<0)
 46   {
 47       perror("sock");
 48       exit(1);
 49   }
 50   struct sockaddr_in local;
 51   local.sin_family=AF_INET;
 52   local.sin_addr.s_addr=inet_addr(ip);
 53   local.sin_port=htons(port);
 54 
 55   //bind
 56   if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
 57   {
 58       perror("bind");
 59       exit(2);
 60   }
 61
 62   if(listen(sock,back_log)<0)
 63   {
 64       perror("listen");
 65       exit(3);
 66   }
 67   return sock;
 68 }
 69 
 70 void usage(char* argv)
 71 {
 72     printf("%s:[ip][port]\n",argv);
 73 }
 74 int main(int argc,char* argv[])
 75 {
 76     if(argc!=3)
 77     {
 78         usage(argv[0]);
 79         exit(1);
 80     }
 81     int port=atoi(argv[2]);
 82     char* ip=argv[1];
 83     int listen_sock=start_up(ip,port);
 84     struct sockaddr_in client;
 85     socklen_t len=sizeof(&client);
 86     while(1)
 87     {
 88      int client_sock=accept(listen_sock,(struct sockaddr*)&client,&len);
 89      if(client_sock<0)
 90     {
 91         perror("accept");
 92     }
 93 printf("[ip]:%s",inet_ntoa(client.sin_addr));
 94 printf("[port]:%ld",ntohs(client.sin_port));
 95 printf("get connection..\n");
 96 #ifdef _v1_
 97     char buf[1024];
 98     while(1)
 99     {
100         memset(buf,‘\0‘,sizeof(buf));
101         ssize_t _s=read(client_sock,buf,sizeof(buf)-1);
102         if(_s>0)
103         {
104             buf[_s]=‘\0‘;
105             printf("client#\n");
106             printf("%s\n",buf);
107     
108         }
109         else if(_s==0)
110         {
111     
112             printf("client closed\n");
113         }
114         else
115         {
116             perror("read");
117             exit(4);
118         }
119     
120     }
121   return 0;
122  }

tcp_client.c:

  1 #include<sys/socket.h>
  2 #include<sys/types.h>
  3 #include<unistd.h>
  4 #include<errno.h>
  5 #include<string.h>
  6 #include<arpa/inet.h>
  7 #include<netinet/in.h>
  8 #include<string.h>
  9 #include<stdlib.h>
 10 #include<stdio.h>
 11 
 12 
 13 void usage(char* proc)
 14 {
 15   printf("Usage:%s[ip][port]\n",proc);
 16 }
 17 int main(int argc,char* argv[])
 18 {
 19     if(argc!=3)
 20     {
 21         usage(argv[0]);
 22         exit(1);
 23     }
 24 char* ip=argv[1];
 25 int port=atoi(argv[2]);
 26 
 27     //socket
 28     int sock=socket(AF_INET,SOCK_STREAM,0);
 29     if(sock<0)
 30     {
 31         perror("sock");
 32         exit(2);
 33     }
 34     struct sockaddr_in remote;
 35     remote.sin_family=AF_INET;
 36     remote.sin_port=htons(port);
 37     remote.sin_addr.s_addr=inet_addr(ip);
 38 
 39      int ret=connect(sock,(struct sockaddr*)&remote,sizeof(remote));
 40      if(ret<0)
 41      {
 42        perror("coneect");
 43      }
 44 
 45      char buf[1024];
 46      while(1)
 47      {
 48          memset(buf,‘\0‘,sizeof(buf));
 49          scanf("%s",buf);
 50          ssize_t _s= write(sock,buf,sizeof(buf)-1);
 51 
 52         if(_s<0)
 53         {
 54             perror("read");
 55         }
 56      }
 57 
 58      return 0;
 59 }

socket通信

服务器:

(1)创建socket描述字 socket();

(2)bind():绑定套接字到本地地址

(3)listen():监听是否有连接请求

(4)accept():接受连接


客户端:

(1)socket():创建套接字

(2)无需绑定,在发送请求时系统已经默认分配了一个端口号且将IP地址和端口号一并发送了过去;

server:

[[email protected] socket]$ ./tcp_server 127.0.0.1 8080

[ip]:236.33.59.0[port]:38650get connection..

client#

we are young


client#

wo ai ni


client:

[[email protected] socket]$ ./tcp_client 127.0.0.1 8080

we are young

wo ai ni


这种方法只能实现一个客户端连接,可以把监听连接和接收数据分隔开,分别交给两个进程去实现,子进程只负责接收数据,关闭监听连接文件描述符,父进程只负责监听。

注意:子进程退出,而父进程还在监听状态,就会造成僵尸进程,解决办法:如果父进程很忙,那么可以用signal函数为SIGCHLD安装handler,因为子进程结束后, 父进程会收到该信号,可以在handler中调用wait回收


代码:

122     pid_t id=fork();
123     if(id==0)
124     {
125         //child
126         close(listen_sock);
127     char buf[1024];
128     while(1)
129     {
130         memset(buf,‘\0‘,sizeof(buf));
131         ssize_t _s=read(client_sock,buf,sizeof(buf)-1);
132         if(_s>0)
133         {
134             buf[_s-1]=‘\0‘;
135             printf("client#\n");
136             printf("%s\n",buf);
137 
138         }
139         else if(_s==0)
140         {
141 
142             printf("client closed\n");
143         }
144         else
145         {
146             perror("read");
147         }
148 
149     close(client_sock);
150     exit(1);
151     }
152     }
153     else if(id>0)
154     {
155 
156         close(client_sock);
157 
158 
159     }
160     else
161     {
162         perror("fork");
163     }

多线程实现:

165     pthread_t tid;
166     pthread_create(&tid,NULL,thread_run,(void*)client_sock);
167     pthread_detach(tid);


 12 void* thread_run(void* argv)
 13 {
 14     int sock=(int)argv;
 15     char buf[1024];
 16     while(1)
 17     {
 18         memset(buf,‘\0‘,sizeof(buf));
 19         ssize_t _s=read(sock,buf,sizeof(buf)-1);
 20         if(_s>0)
 21         {
 22             buf[_s]=‘\0‘;
 23             printf("client#\n");
 24             printf("%s\n",buf);
 25 
 26         }
 27         else if(_s==0)
 28         {
 29 
 30             printf("client closed\n");
 31         }
 32         else
 33         {
 34             perror("read");
 35             exit(4);
 36         }
 37     }
 38     close(sock);
 39     return NULL;
 40 }

 线程分离,等到接收数据完毕后确认释放连接后,自动回收资源。

[ip]:169.138.4.8[port]:38656get connection..
client#
nihao

[ip]:127.0.0.1[port]:38657get connection..
client#
lall


以上是关于网络中进程通信-----socket的主要内容,如果未能解决你的问题,请参考以下文章

Python全栈开发之10网络编程

Socket~UDP

socket编程

最新-iOS Socket

Socket了解

socket概述和字节序地址转换函数