前置知识

网络进程标识: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); //返回sockfd

该函数用于创建一个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()时系统会随机分配一个端口号。

函数的三个参数分别为:

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

  • addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同,如ipv4对应的是:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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 */
    };

    ipv6对应的是:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    struct sockaddr_in6 { 
    sa_family_t sin6_family; /* AF_INET6 */
    in_port_t sin6_port; /* port number */
    uint32_t sin6_flowinfo; /* IPv6 flow information */
    struct in6_addr sin6_addr; /* IPv6 address */
    uint32_t sin6_scope_id; /* Scope ID (new in 2.4) */
    };

    struct in6_addr {
    unsigned char s6_addr[16]; /* IPv6 address */
    };

    Unix域对应的是:

    1
    2
    3
    4
    5
    6
    #define UNIX_PATH_MAX    108

    struct sockaddr_un {
    sa_family_t sun_family; /* AF_UNIX */
    char sun_path[UNIX_PATH_MAX]; /* pathname */
    };
  • addrlen:对应的是地址的长度。

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);//返回connect_fd

该函数用于服务器端在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()函数

1
int close(int fd);

该函数用于关闭连接。

注: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的半关闭造成的。

  1. 客户端发送一个FIN,用来关闭客户端到服务器端的数据传送。
  2. 服务器端收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。
  3. 服务器端关闭与客户端的连接,发送一个FIN给客户端。
  4. 客户端发回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