什么是Socket?
Socket是一个通信的端点。一对进程在网络直接通过一对socket通信,每个进程一个。
一个socket由一个IP地址和端口号确定。Socket封装了一些操作,使得网络里两个进程的数据通信比较方便。基于TCP协议和UDP协议的socket用得很多。
下图展示了两种方式的通信过程

②建立socket进行通信使用的几个函数
以Linux系统为例,说明一下几个函数
A. socket()函数
- int socket(int domain, int type, int protocol);
这个操作类似于打开文件操作,返回socket的socket描述符。
参数:
domain:协议域,又称为协议族(family)。常用的协议族有AF_INET、AF_INET6、AF_LOCAL、AF_ROUTE。协议族决定了socket的地址类型,通信时采用与其相符的地址,AF_INET用ipv4地址(32位)和16位端口号的组合
type:指定socket类型,常用的有SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET,前两个分别对应TCP和UDP类型的socket
protocol:指定协议,常用有IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC,协议和socket的类型要匹配。0会选择type对应的默认类型。
B. bind()函数
- int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
把一个地址族的特定地址指定给socket,而不是由系统随机分配.
参数:
sockfd:socket描述符,socket()函数返回的int值
addr:一个地址结构体的const指针,指向要绑定给sockfd的地址,结构体的结构和地址协议相符。
如ipv4的地对应的
- struct sockaddr_in {
- sa_family_t sin_family;
- in_port_t sin_port;
- struct in_addr sin_addr;
- };
-
- struct in_addr {
- uint32_t s_addr;
- };
应该注意使用htol,htos函数将主机字节顺序转换为网络字节顺序,避免潜在的错误。
C. listen()、connect()函数
使用时依次调用socket(),connect(),然后调用listen()来监听socket,客户端调用connect是,服务器就会收到这个请求。
- int listen(int sockfd, int backlog);
sockfd是要监听的socket的描述符
backlog是这个socket可以排队连接的最大链接个数,也就是这个socket的等待队列的长度。调用listen,socket开始等待客户的链接请求
- int connect(int sockfd, const structsockaddr *addr, socklen_t addrlen);
sockfd 是客户端socket描述字
addr为服务器的socket地址
addr_len是socket地址的长度。
客户端通过调用connect函数来建立与TCP服务器的连接。调用listen(),socket开始等待客户的链接请求
D. accept()函数
服务器端第四个要调用的函数,服务器收到请求后,用accept接受请求,然后链接就建立了,可以开始读写操作。
- int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
sockfd是服务器socket描述字
addr是指针,用于返回客户端地址
addrlen是协议地址的长度。函数的返回值是内核自动生成的一个全新的描述字,代表一个和客户端的TCP链接
E.read(),write()读写操作
相关函数原型如下所示
- #include <unistd.h>
- ssize_t read(int fd, void *buf, size_t count);
- ssize_t write(int fd, const void *buf, size_t count);
-
- #include <sys/types.h>
- #include <sys/socket.h>
-
- ssize_t send(int sockfd, const void *buf, size_t len, int flags);
- ssize_t recv(int sockfd, void *buf, size_t len, int flags);
-
- ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
- const struct sockaddr*dest_addr, socklen_t addrlen);
- ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
- struct sockaddr*src_addr, socklen_t *addrlen);
-
- ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
- ssize_t recvmsg(int sockfd, struct msghdr*msg, int flags);
E. close()函数
读写完毕后要关闭相应的socket描述字
- #include <unistd.h>
- int close(int fd);
③代码示例
代码源自http://www.embedu.org/column/column179.htm
TCP socket
TCP 服务端
- #include <stdio.h>
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <arpa/inet.h>
- #include <stdlib.h>
- #include <string.h>
- #include <unistd.h>
- #include <netinet/in.h>
- #include <errno.h>
-
-
- #define MAX_MSG_SIZE 256
- #define SERVER_PORT 9987
-
-
- #define BACKLOG 2
-
-
- int GetServerAddr(char * addrname){
- printf("please input server addr:");
- scanf("%s",addrname);
- return 1;
- }
-
-
- int main(){
- int sock_fd,client_fd;
- struct sockaddr_in ser_addr;
- struct sockaddr_in cli_addr;
- char msg[MAX_MSG_SIZE];
- int ser_sockfd=socket(AF_INET,SOCK_STREAM,0);
- if(ser_sockfd<0)
- {
- fprintf(stderr,"socker Error:%s\n",strerror(errno));
- exit(1);
- }
-
- socklen_t addrlen=sizeof(struct sockaddr_in);
- bzero(&ser_addr,addrlen);
- ser_addr.sin_family=AF_INET;
- ser_addr.sin_addr.s_addr=htonl(INADDR_ANY);
- ser_addr.sin_port=htons(SERVER_PORT);
- if(bind(ser_sockfd,(struct sockaddr*)&ser_addr,sizeof(struct sockaddr_in))<0){
- fprintf(stderr,"Bind Error:%s\n",strerror(errno));
- exit(1);
- }
-
- if(listen(ser_sockfd,BACKLOG)<0){
- fprintf(stderr,"Listen Error:%s\n",strerror(errno));
- close(ser_sockfd);
- exit(1);
- }
- while(1){
- int cli_sockfd=accept(ser_sockfd,(struct sockaddr*) &cli_addr, &addrlen);
- if(cli_sockfd<=0){
- fprintf(stderr,"Accept Error:%s\n",strerror(errno));
- }else{
- recv(cli_sockfd, msg, (size_t)MAX_MSG_SIZE, 0);
- printf("received a connection from %sn", inet_ntoa(cli_addr.sin_addr));
- printf("%s\n",msg);
- strcpy(msg,"hi,I am server!");
- send(cli_sockfd, msg, sizeof(msg),0);
- close(cli_sockfd);
- }
- }
- close(ser_sockfd);
- return 0;
- }
TCP 客户端
- #include <stdio.h>
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <arpa/inet.h>
- #include <stdlib.h>
- #include <string.h>
- #include <unistd.h>
- #include <netinet/in.h>
- #include <errno.h>
-
-
- #define MAX_MSG_SIZE 256
- #define SERVER_PORT 9987
-
-
- int GetServerAddr(char * addrname){
- printf("please input server addr:");
- scanf("%s",addrname);
- return 1;
- }
-
-
- int main(){
- int cli_sockfd;
- int addrlen;
- char seraddr[14];
- struct sockaddr_in ser_addr,
- cli_addr;
- char msg[MAX_MSG_SIZE];
-
- GetServerAddr(seraddr);
-
- cli_sockfd=socket(AF_INET,SOCK_STREAM,0);
-
- if(cli_sockfd<0){
- fprintf(stderr,"socker Error:%s\n",strerror(errno));
- exit(1);
- }
-
- addrlen=sizeof(struct sockaddr_in);
- bzero(&ser_addr,addrlen);
- cli_addr.sin_family=AF_INET;
- cli_addr.sin_addr.s_addr=htonl(INADDR_ANY);
- cli_addr.sin_port=0;
- if(bind(cli_sockfd,(struct sockaddr*)&cli_addr,addrlen)<0){
-
- fprintf(stderr,"Bind Error:%s\n",strerror(errno));
- exit(1);
- }
-
- addrlen=sizeof(struct sockaddr_in);
- bzero(&ser_addr,addrlen);
- ser_addr.sin_family=AF_INET;
- ser_addr.sin_addr.s_addr=inet_addr(seraddr);
- ser_addr.sin_port=htons(SERVER_PORT);
- if(connect(cli_sockfd,(struct sockaddr*)&ser_addr, addrlen)!=0)
- {
-
- fprintf(stderr,"Connect Error:%s\n",strerror(errno));
- close(cli_sockfd);
- exit(1);
- }
- strcpy(msg,"hi,I am client!");
- send(cli_sockfd, msg, sizeof(msg),0);
- recv(cli_sockfd, msg, MAX_MSG_SIZE,0);
- printf("%s\n",msg);
- close(cli_sockfd);
-
- return 0;
- }
UDP Socket
UDP服务端
- int main(){
- int ser_sockfd;
- int len;
-
- socklen_t addrlen;
- char seraddr[100];
- struct sockaddr_in ser_addr;
-
- ser_sockfd=socket(AF_INET,SOCK_DGRAM,0);
- if(ser_sockfd<0){
- printf("I cannot socket success\n");
- return 1;
- }
-
- addrlen=sizeof(struct sockaddr_in);
- bzero(&ser_addr,addrlen);
- ser_addr.sin_family=AF_INET;
- ser_addr.sin_addr.s_addr=htonl(INADDR_ANY);
- ser_addr.sin_port=htons(SERVER_PORT);
-
- if(bind(ser_sockfd,(struct sockaddr *)&ser_addr,addrlen)<0){
- printf("connect");
- return 1;
- }
- while(1){
- bzero(seraddr,sizeof(seraddr));
- len=recvfrom(ser_sockfd,seraddr,sizeof(seraddr),0,(struct sockaddr*)&ser_addr,&addrlen);
-
- printf("receive from %s\n",inet_ntoa(ser_addr.sin_addr));
-
- printf("recevce:%s",seraddr);
-
- sendto(ser_sockfd,seraddr,len,0,(struct sockaddr*)&ser_addr,addrlen);
- }
-
- return 0;
- }
UDP客户端
- int GetServerAddr(char * addrname){
- printf("please input server addr:");
- scanf("%s",addrname);
- return 1;
- }
- int main(int argc,char **argv){
- int cli_sockfd;
- int len;
- socklen_t addrlen;
- char seraddr[14];
- struct sockaddr_in cli_addr;
- char buffer[256];
- GetServerAddr(seraddr);
-
- cli_sockfd=socket(AF_INET,SOCK_DGRAM,0);
- if(cli_sockfd<0){
- printf("I cannot socket success\n");
- return 1;
- }
-
- addrlen=sizeof(struct sockaddr_in);
- bzero(&cli_addr,addrlen);
- cli_addr.sin_family=AF_INET;
- cli_addr.sin_addr.s_addr=inet_addr(seraddr);
-
- cli_addr.sin_port=htons(SERVER_PORT);
-
- bzero(buffer,sizeof(buffer));
-
- len=read(STDIN_FILENO,buffer,sizeof(buffer));
-
- sendto(cli_sockfd,buffer,len,0,(struct sockaddr*)&cli_addr,addrlen);
-
- len=recvfrom(cli_sockfd,buffer,sizeof(buffer),0,(struct sockaddr*)&cli_addr,&addrlen);
-
- printf("receive: %s",buffer);
- close(cli_sockfd);
- return 0;
- }
socket中的TCP_IP的三次握手与四次挥手:
(一)socket中TCP的三次握手建立连接:
(1)tcp建立连接要进行“三次握手”,即交换三个分组。大致流程如下:
客户端向服务器发送一个SYN J
服务器向客户端响应一个SYN K,并对SYN J进行确认ACK J+1
客户端再想服务器发一个确认ACK K+1
只有就完了三次握手,但是这个三次握手发生在socket的那几个函数中呢?请看下图:
(2)示意图:

(3)步骤:
1.当客户端调用connect时,触发了连接请求,向服务器发送了SYN J包,这时connect进入阻塞状态;
2.服务器监听到连接请求,即收到SYN J包,调用accept函数接收请求向客户端发送SYN K ,ACK J+1,这时accept进入阻塞状态;
3.客户端收到服务器的SYN K ,ACK J+1之后,这时connect返回,并对SYN K进行确认;服务器收到ACK K+1时,accept返回,至此三次握手完毕,连接建立。
(4)总结:
客户端的connect在三次握手的第二个次返回,而服务器端的accept在三次握手的第三次返回。
--------------
(二)socket中TCP的四次挥手释放连接:
(1)示意图:

(2)过程:
1.某个应用进程首先调用close主动关闭连接,这时TCP发送一个FIN M;
2.另一端接收到FIN M之后,执行被动关闭,对这个FIN进行确认。它的接收也作为文件结束符传递给应用进程,因为FIN的接收意味着应用进程在相应的连接上再也接收不到额外数据;
3.一段时间之后,接收到文件结束符的应用进程调用close关闭它的socket。这导致它的TCP也发送一个FIN N;
4.接收到这个FIN的源发送端TCP对它进行确认。
说明:
本文由giantpoplar发表于优快云
文章地址 http://blog.youkuaiyun.com/giantpoplar/article/details/47657303
转载请保留本说明