前置知识
网络进程标识:ip地址,协议,端口。
TCP/IP是传输控制协议/网间协议。
TCP/IP协议存在于OS中,网络服务通过OS提供,在OS中增加支持TCP/IP的系统调用。
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。
Socket API
socket()函数
1
| int socket(int protofamily, int typ, int protocol);
|
该函数用于创建一个socket描述符,它唯一标识一个socket。其中三个参数分别如下:
- protofamily:即协议域,又称为协议族(family)。常用的协议族有,AF_INET(IPV4)、AF_INET6(IPV6)、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。当其为0时,会自动选择type对应的默认协议。
- type:指定socket类型。常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等。
- protocol:顾名思意,就是指定协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。
注:protofamily与type不可随意组合。
bind()函数
1
| int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
|
该函数用来把一个特定地址赋给sockfd,如127.0.0.1:80。若未用该函数直接connect(),list()时系统会随机分配一个端口号。
函数的三个参数分别为:
list()、connect()函数
1 2
| int list(int sockfd, int backlog); int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
|
list()函数用于服务器端侦听端口。
connect()函数用于客户端向服务器端发送连接请求。
accept()函数
1
| int accept(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
|
该函数用于服务器端在list()之后接收请求,其中第二个参数addr用来接收返回值,返回值指向客户端地址。
注:accept默认会阻塞进程,直到有一个客户连接建立后返回。
读写操作函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #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);
|
这些函数用于接收和发送数据,不一一介绍,具体看man文档。
close()函数
该函数用于关闭连接。
注:close操作只是使用相应socket描述字的引用计数减1,只有当引用计数为0时,才会触发TCP客户端向服务器发送终止连接请求。
socket中TCP的建立
TCP协议通过三个报文段完成连接的建立,即三次握手:
- 第一次握手:建立连接时,客户端发送syn包到服务器,并进入SYN_SEND状态,等待服务器确认。(SYN:同步序列编号)
- 第二次握手:服务器收到syn包,必须确认客户的SYN,同时自己也发送一个SYN包,即SYN+ACK包,此时服务器进入SYN_RECV状态。
- 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK,此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
TCP连接的终止
终止一个连接要经过四次握手,这是由TCP的半关闭造成的。
- 客户端发送一个FIN,用来关闭客户端到服务器端的数据传送。
- 服务器端收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。
- 服务器端关闭与客户端的连接,发送一个FIN给客户端。
- 客户端发回ACK报文确认,并将确认序号设置为收到序号加1。
socket编程实例
C
服务器端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| #include<stdio.h> #include<stdlib.h> #include<string.h> #include<errno.h> #include<sys/types.h> #include<sys/socket.h> #include<netinet/in.h>
#define BUFSIZE 4096 int main(int argc, char** argv){ int socket_fd, connect_fd; struct sockaddr_in servaddr; char buf[BUFSIZE]; int n; if( (socket_fd = socket(AF_INET, SOCK_STREAM, 0) ) == -1 ){ printf("create socket error: %s(errno: %d)\n",strerror(errno), errno); exit(0); } memset(&servaddr, 0, sizeof(servaddr) ); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(8000); if(bind(socket_fd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){ printf("bind socket error: %s(errno: %d)\n",strerror(errno), errno); exit(0); } if(listen(socket_fd, 10) == -1){ printf("listen socket error: %s(errno: %d)\n",strerror(errno), errno); exit(0); } printf("=====wait for connect====\n"); while(1){ if( (connect_fd = accept(socket_fd, (struct sockaddr*)NULL, NULL) ) == -1){ printf("accept socket error: %s(errno: %d)\n",strerror(errno), errno); continue; } n = recv(connect_fd, buf, BUFSIZE, 0); if(!fork()){ if(send(connect_fd, "Hello, you are connect", 27, 0) == -1) perror("send_error"); close(connect_fd); exit(0); } buf[n] = '\0'; printf("you have recv msg: %s\n",buf); close(connect_fd); } close(socket_fd); return 0; }
|
客户端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| #include<stdio.h> #include<stdlib.h> #include<string.h> #include<errno.h> #include<sys/types.h> #include<sys/socket.h> #include<netinet/in.h>
#define BUFSIZE 4096
int main(int argc, char** argv){ int socket_fd, n, rec_len; char sendline[BUFSIZE]; char buf[BUFSIZE]; struct sockaddr_in servaddr; if(argc < 2){ printf("usage: ./client <ipaddress>"); exit(0); } if( (socket_fd = socket(AF_INET, SOCK_STREAM, 0) ) < 0 ){ printf("create socket error: %s(errno: %d)\n", strerror(errno), errno); exit(0); } memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(8000); if( inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0){ printf("inet_pton error for %s\n", argv[1]); exit(0); } if(connect(socket_fd, (struct sockaddr*)&servaddr, sizeof(servaddr) ) < 0 ){ printf("connect socket error: %s(errno: %d)\n", strerror(errno), errno); exit(0); } printf("send msg to server: \n"); fgets(sendline, BUFSIZE, stdin); if(send(socket_fd, sendline, strlen(sendline), 0) < 0){ printf("send msg error:%s(errno: %d)\n", strerror(errno), errno); exit(0); } if( (rec_len = recv(socket_fd, buf, BUFSIZE, 0) ) == -1 ){ printf("recv msg error:%s(errno: %d)\n", strerror(errno), errno); exit(0); } buf[rec_len] = '\0'; printf("Recvived : %s\n", buf); close(socket_fd); return 0; }
|
python
服务器端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import socket s = socket.socket() host = socket.gethostname() port = 8000 s.bind((host, port))
s.listen(5) while True: c,addr = s.accept() print 'address',addr msg = "welcome to my service. \nif you want to exit, please input exit." while(True): c.send(msg) buf = c.recv(1024) if 'exit' in buf: c.close() break msg = "you have input: "+buf
|
客户端
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import socket import sys s = socket.socket() host = socket.gethostname() port = 8000 s.connect((host, port)) print s.recv(1024) while(True): buf = sys.stdin.readline() s.send(buf) print s.recv(1024) if 'exit' in buf: s.close() break
|
参考资料
Linux的SOCKET编程详解 - 江召伟 - 博客园 https://www.cnblogs.com/jiangzhaowei/p/8261174.html
Python 网络编程 | 菜鸟教程 https://www.runoob.com/python/python-socket.html