标签:socket通信
一、什么是socket?
socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。我的理解就是Socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)。
int socket(int domain, int type, int protocol);
socket函数对应于普通文件的打开操作。普通文件的打开操作返回一个文件描述字,而socket()用于创建一个socket描述符(socket descriptor),它唯一标识一个socket。这个socket描述字跟文件描述字一样,后续的操作都有用到它,把它作为参数,通过它来进行一些读写操作。
正如可以给fopen的传入不同参数值,以打开不同的文件。创建socket的时候,也可以指定不同的参数创建不同的socket描述符,socket函数的三个参数分别为:
domain:即协议域,又称为协议族(family)。常用的协议族有,AF_INET、AF_INET6、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。
type:指定socket类型。常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等(socket的类型有哪些?)。
protocol:故名思意,就是指定协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。
当我们调用socket创建一个socket时,返回的socket描述字它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()、listen()时系统会自动随机分配一个端口。
(2)bind()函数
bind()函数把一个地址族中的特定地址赋给socket。例如对应AF_INET、AF_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函数为客户端用来与服务器创建连接。
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:
[admin@www 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:
[admin@www 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通信
原文地址:http://youngyoungla.blog.51cto.com/10697042/1782653