0%

UNIX编程 TCP基础读写笔记

基本TCP客户端与服务器

Server

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
#include <cstdio>
#include <cstring>

#include <string>
#include <vector>

#include <unistd.h>
#include <arpa/inet.h>
#include <sys/errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string>

using namespace std;

const int BUF_SIZE = 1024;

string addr_to_string(const sockaddr_in *addr) {
char addr_str[INET_ADDRSTRLEN];
const char *addr_str_ptr = inet_ntop(AF_INET, &addr->sin_addr, addr_str, sizeof(addr_str));
if (addr_str_ptr == NULL) {
fprintf(stderr, "inet_ntop failed\n");
return "";
}
return string(addr_str_ptr) + ":" + to_string(ntohs(addr->sin_port));
}

string get_sockname_string(int sockfd) {
sockaddr_in addr;
socklen_t len = sizeof(addr);
if (getsockname(sockfd, (sockaddr *) &addr, &len) == -1) {
fprintf(stderr, "getsockname failed\n");
return "";
}
return addr_to_string(&addr);
}

string get_peername_string(int sockfd) {
sockaddr_in addr;
socklen_t len = sizeof(addr);
if (getpeername(sockfd, (sockaddr *) &addr, &len) == -1) {
fprintf(stderr, "getpeername failed, errno: %d, str: %s\n", errno, strerror(errno));
return "";
}
return addr_to_string(&addr);
}


int init_server(int type, const sockaddr *addr, socklen_t alen, int qlen) {
int fd;
if ((fd = socket(addr->sa_family, type, 0)) == -1) {
fprintf(stderr, "create socket failed, errno: %d, str: %s\n", errno, strerror(errno));
return -1;
}
printf("create socket fd: %d, sock_name: %s, peer_name: %s\n", fd, get_sockname_string(fd).c_str(), get_peername_string(fd).c_str());

if (bind(fd, addr, alen) == -1) {
fprintf(stderr, "bind failed, errno: %d, str: %s\n", errno, strerror(errno));
close(fd);
return -1;
}
printf("bind socket fd: %d, sock_name: %s, peer_name: %s\n", fd, get_sockname_string(fd).c_str(), get_peername_string(fd).c_str());

if (type == SOCK_STREAM || type == SOCK_SEQPACKET) {
if (listen(fd, qlen) == -1) {
fprintf(stderr, "listen failed, errno: %d, str: %s\n", errno, strerror(errno));
close(fd);
return -1;
}
printf("listen fd: %d, sock_name: %s, peer_name: %s\n", fd, get_sockname_string(fd).c_str(), get_peername_string(fd).c_str());
}
return fd;
}

int main(int argc, char **argv) {
int sleep_before_recv = 0;
int sleep_after_listen = 0;
int qlen = 0;
int port = 27015;
char *ip = "0.0.0.0";

for (int i = 1; i < argc;) {
if (argv[i] == string("--port")) {
port = atoi(argv[i + 1]);
i += 2;
continue;
}
if (argv[i] == string("--qlen")) {
qlen = atoi(argv[i + 1]);
i += 2;
continue;
}
if (argv[i] == string("--sleep-before-recv")) {
sleep_before_recv = atoi(argv[i + 1]);
i += 2;
continue;
}
if (argv[i] == string("--sleep-after-listen")) {
sleep_after_listen = atoi(argv[i + 1]);
i += 2;
continue;
}
if (argv[i] == string("--ip")) {
ip = argv[i + 1];
i += 2;
continue;
}
i++;
}

printf("port: %d, qlen: %d, sleep-before-recv: %d, sleep-after-listen: %d\n", port, qlen, sleep_before_recv, sleep_after_listen);

sockaddr_in addr;
bzero(&addr, sizeof(addr)); // APUE 16.3.2 其中成员sin_zero为填充字段,应该全部被置为0
addr.sin_family = AF_INET;
inet_pton(addr.sin_family, ip, &addr.sin_addr);
addr.sin_port = htons(port);

int sockfd = init_server(SOCK_STREAM, (sockaddr *) &addr, sizeof(addr), qlen);
if (sockfd == -1) return -1;

sleep(sleep_after_listen);

while (true) {
int clfd, n;
sockaddr_in accepted_addr;
socklen_t len = sizeof(accepted_addr);

if ((clfd = accept(sockfd, (sockaddr *) &accepted_addr, &len)) == -1) {
fprintf(stderr, "accept failed, errno: %d, str: %s\n", errno, strerror(errno));
continue;
}
printf("after accept sockfd: %d, sock_name: %s, peer_name: %s\n",
sockfd, get_sockname_string(sockfd).c_str(), get_peername_string(sockfd).c_str());
printf("accepted fd: %d, sock_name: %s, peer_name: %s, addr: %s\n",
clfd, get_sockname_string(clfd).c_str(), get_peername_string(clfd).c_str(), addr_to_string(&accepted_addr).c_str());

sleep(sleep_before_recv);
char buf[BUF_SIZE];
if ((n = recv(clfd, buf, BUF_SIZE - 1, 0)) == -1) {
fprintf(stderr, "recv failed, errno: %d, str: %s\n", errno, strerror(errno));
close(clfd);
continue;
}
buf[n] = '\0';
printf("read %d bytes from fd: %d, buf: %s\n", n, clfd, buf);
close(clfd);
}

return 0;
}

如果调用getsockname时没有地址绑定到传入的套接字,则其结果是未定义的。1

Client

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#include <cstdio>
#include <cstring>

#include <vector>
#include <string>

#include <unistd.h>
#include <arpa/inet.h>
#include <sys/errno.h>
#include <sys/socket.h>
#include <netinet/in.h>

using namespace std;

const int BUF_SIZE = 1024;

string addr_to_string(const sockaddr_in *addr) {
char addr_str[INET_ADDRSTRLEN];
const char *addr_str_ptr = inet_ntop(AF_INET, &addr->sin_addr, addr_str, sizeof(addr_str));
if (addr_str_ptr == NULL) {
fprintf(stderr, "inet_ntop failed\n");
return "";
}
return string(addr_str_ptr) + ":" + to_string(ntohs(addr->sin_port));
}

string get_sockname_string(int sockfd) {
sockaddr_in addr;
socklen_t len = sizeof(addr);
if (getsockname(sockfd, (sockaddr *) &addr, &len) == -1) {
fprintf(stderr, "getsockname failed\n");
return "";
}
return addr_to_string(&addr);
}

string get_peername_string(int sockfd) {
sockaddr_in addr;
socklen_t len = sizeof(addr);
if (getpeername(sockfd, (sockaddr *) &addr, &len) == -1) {
fprintf(stderr, "getpeername failed, errno: %d, str: %s\n", errno, strerror(errno));
return "";
}
return addr_to_string(&addr);
}

int main(int argc, char **argv) {
int port = 27015;
char *ip = "0.0.0.0";
for (int i = 1; i < argc;) {
if (argv[i] == string("--port")) {
port = atoi(argv[i + 1]);
i += 2;
continue;
}
if (argv[i] == string("--ip")) {
ip = argv[i + 1];
i += 2;
continue;
}
i++;
}


sockaddr_in addr;
bzero(&addr, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
if (inet_pton(addr.sin_family, ip, &addr.sin_addr) <= 0) {
fprintf(stderr, "inet_pton failed\n");
return -1;
}

int sockfd = socket(addr.sin_family, SOCK_STREAM, 0);
if (sockfd == -1) {
fprintf(stderr, "create socket failed, errno: %d, str: %s\n", errno, strerror(errno));
return -1;
}
printf("fd: %d, sock_name: %s, peer_name: %s\n", sockfd, get_sockname_string(sockfd).c_str(), get_peername_string(sockfd).c_str());

if (connect(sockfd, (sockaddr *) &addr, sizeof(addr)) == -1) {
fprintf(stderr, "connect failed, errno: %d, str: %s\n", errno, strerror(errno));
close(sockfd);
return -1;
}
printf("fd: %d, sock_name: %s, peer_name: %s\n", sockfd, get_sockname_string(sockfd).c_str(), get_peername_string(sockfd).c_str());

vector<string> msgs = {"hello", "world", "cpp"};
for (const auto &msg : msgs) {
int n = send(sockfd, msg.c_str(), msg.size(), 0);
if (n == -1) {
fprintf(stderr, "send failed, errno: %d, str: %s\n", errno, strerror(errno));
break;
}
printf("fd: %d, send %d bytes\n", sockfd, n);
}
close(sockfd);

return 0;
}

Read

建连后客户端调用三次send,服务端在recv之前sleep 0秒

server:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ ./server --sleep-before-recv 0
port: 27015, qlen: 0, sleep-before-recv: 0, sleep-after-listen: 0
getpeername failed, errno: 57, str: Socket is not connected
create socket fd: 3, sock_name: 0.0.0.0:0, peer_name:
getpeername failed, errno: 57, str: Socket is not connected
bind socket fd: 3, sock_name: 0.0.0.0:27015, peer_name:
getpeername failed, errno: 57, str: Socket is not connected
listen fd: 3, sock_name: 0.0.0.0:27015, peer_name:
getpeername failed, errno: 57, str: Socket is not connected
after accept sockfd: 3, sock_name: 0.0.0.0:27015, peer_name:
accepted fd: 4, sock_name: 127.0.0.1:27015, peer_name: 127.0.0.1:59932, addr: 127.0.0.1:59932
read 5 bytes from fd: 4, buf: hello
^C

client:

1
2
3
4
5
6
7
$ ./client
getpeername failed, errno: 57, str: Socket is not connected
fd: 3, sock_name: 0.0.0.0:0, peer_name:
fd: 3, sock_name: 127.0.0.1:59932, peer_name: 127.0.0.1:27015
fd: 3, send 5 bytes
fd: 3, send 5 bytes
fd: 3, send 3 bytes

建连后客户端调用三次send,服务端在recv之前sleep 5秒

server:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ ./server --sleep-before-recv 5
port: 27015, qlen: 0, sleep-before-recv: 5, sleep-after-listen: 0
getpeername failed, errno: 57, str: Socket is not connected
create socket fd: 3, sock_name: 0.0.0.0:0, peer_name:
getpeername failed, errno: 57, str: Socket is not connected
bind socket fd: 3, sock_name: 0.0.0.0:27015, peer_name:
getpeername failed, errno: 57, str: Socket is not connected
listen fd: 3, sock_name: 0.0.0.0:27015, peer_name:
getpeername failed, errno: 57, str: Socket is not connected
after accept sockfd: 3, sock_name: 0.0.0.0:27015, peer_name:
accepted fd: 4, sock_name: 127.0.0.1:27015, peer_name: 127.0.0.1:60018, addr: 127.0.0.1:60018
# 五秒后
read 13 bytes from fd: 4, buf: helloworldcpp
^C

client: 先执行服务端,再执行客户端,客服端执行完三次send后未等服务端read就已返回

1
2
3
4
5
6
7
$ ./client
getpeername failed, errno: 57, str: Socket is not connected
fd: 3, sock_name: 0.0.0.0:0, peer_name:
fd: 3, sock_name: 127.0.0.1:60018, peer_name: 127.0.0.1:27015
fd: 3, send 5 bytes
fd: 3, send 5 bytes
fd: 3, send 3 bytes

Listen Backlog

backlog传2

server:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ ./server --sleep-after-listen 10 --qlen 2
port: 27015, qlen: 2, sleep-before-recv: 0, sleep-after-listen: 10
getpeername failed, errno: 57, str: Socket is not connected
create socket fd: 3, sock_name: 0.0.0.0:0, peer_name:
getpeername failed, errno: 57, str: Socket is not connected
bind socket fd: 3, sock_name: 0.0.0.0:27015, peer_name:
getpeername failed, errno: 57, str: Socket is not connected
listen fd: 3, sock_name: 0.0.0.0:27015, peer_name:
# sleep here
getpeername failed, errno: 57, str: Socket is not connected
after accept sockfd: 3, sock_name: 0.0.0.0:27015, peer_name:
accepted fd: 4, sock_name: 127.0.0.1:27015, peer_name: 127.0.0.1:63689, addr: 127.0.0.1:63689
read 13 bytes from fd: 4, buf: helloworldcpp
getpeername failed, errno: 57, str: Socket is not connected
after accept sockfd: 3, sock_name: 0.0.0.0:27015, peer_name:
accepted fd: 4, sock_name: 127.0.0.1:27015, peer_name: 127.0.0.1:63690, addr: 127.0.0.1:63690
read 13 bytes from fd: 4, buf: helloworldcpp
getpeername failed, errno: 57, str: Socket is not connected
after accept sockfd: 3, sock_name: 0.0.0.0:27015, peer_name:
accepted fd: 4, sock_name: 127.0.0.1:27015, peer_name: 127.0.0.1:63691, addr: 127.0.0.1:63691
read 13 bytes from fd: 4, buf: helloworldcpp
^C

client:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ ./client
getpeername failed, errno: 57, str: Socket is not connected
fd: 3, sock_name: 0.0.0.0:0, peer_name:
fd: 3, sock_name: 127.0.0.1:63689, peer_name: 127.0.0.1:27015
fd: 3, send 5 bytes
fd: 3, send 5 bytes
fd: 3, send 3 bytes
$ ./client
getpeername failed, errno: 57, str: Socket is not connected
fd: 3, sock_name: 0.0.0.0:0, peer_name:
fd: 3, sock_name: 127.0.0.1:63690, peer_name: 127.0.0.1:27015
fd: 3, send 5 bytes
fd: 3, send 5 bytes
fd: 3, send 3 bytes
$ ./client
getpeername failed, errno: 57, str: Socket is not connected
fd: 3, sock_name: 0.0.0.0:0, peer_name:
# block here
fd: 3, sock_name: 127.0.0.1:63691, peer_name: 127.0.0.1:27015
fd: 3, send 5 bytes
fd: 3, send 5 bytes
fd: 3, send 3 bytes

backlog传0

参数backlog提供了一个提示,提示系统该进程所要入队的未完成连接请求数量。其实际值由系统决定。具体的最大值取决于每个协议的实现。对于TCP,其默认值为128。2 通过下面的脚本,在macOs上测试,验证了TCP的默认值为128。

run-server.sh

1
2
3
4
5
6
7
#!/bin/bash

for i in {1..256}; do
echo "do $i"
./client 1>/dev/null 2>&1
echo "$i finished"
done

server:

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
$ ./server --sleep-after-listen 10
port: 27015, qlen: 0, sleep-before-recv: 0, sleep-after-listen: 10
getpeername failed, errno: 57, str: Socket is not connected
create socket fd: 3, sock_name: 0.0.0.0:0, peer_name:
getpeername failed, errno: 57, str: Socket is not connected
bind socket fd: 3, sock_name: 0.0.0.0:27015, peer_name:
getpeername failed, errno: 57, str: Socket is not connected
listen fd: 3, sock_name: 0.0.0.0:27015, peer_name:
# sleep here
getpeername failed, errno: 57, str: Socket is not connected
after accept sockfd: 3, sock_name: 0.0.0.0:27015, peer_name:
accepted fd: 4, sock_name: 127.0.0.1:27015, peer_name: 127.0.0.1:63789, addr: 127.0.0.1:63789
read 13 bytes from fd: 4, buf: helloworldcpp
getpeername failed, errno: 57, str: Socket is not connected
after accept sockfd: 3, sock_name: 0.0.0.0:27015, peer_name:
accepted fd: 4, sock_name: 127.0.0.1:27015, peer_name: 127.0.0.1:63790, addr: 127.0.0.1:63790
read 13 bytes from fd: 4, buf: helloworldcpp
................
getpeername failed, errno: 57, str: Socket is not connected
after accept sockfd: 3, sock_name: 0.0.0.0:27015, peer_name:
accepted fd: 4, sock_name: 127.0.0.1:27015, peer_name: 127.0.0.1:64050, addr: 127.0.0.1:64050
read 5 bytes from fd: 4, buf: hello
getpeername failed, errno: 57, str: Socket is not connected
after accept sockfd: 3, sock_name: 0.0.0.0:27015, peer_name:
accepted fd: 4, sock_name: 127.0.0.1:27015, peer_name: 127.0.0.1:64051, addr: 127.0.0.1:64051
read 13 bytes from fd: 4, buf: helloworldcpp
^C

client:

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
$ ./run-server.sh
do 1
1 finished
do 2
2 finished
do 3
3 finished
do 4
4 finished
..................
do 126
126 finished
do 127
127 finished
do 128
128 finished
do 129
# block here
129 finished
do 130
130 finished
do 131
131 finished
do 132
132 finished
..................
do 255
255 finished
do 256
256 finished

INADDR_ANY

对于因特网域,如果指定IP地址为INADDR_ANY(<netinet/in.h>中定义的),套接字端点可以被绑定到所有的系统网络接口上。这意味着可以接收这个系统所安装的任何一个网卡的数据包。如果调用connect或listen,但没有将地址绑定到套接字上,系统会选择一个地址绑定到套接字上。3

可以注意到下面在bind 0.0.0.0后的sockfd在getsockname后得到的ip是0.0.0.0,在accept得到的fd上getsockname后得到的ip才是127.0.0.1other-ip,而bind other-ip后的sockfd在getsockname后得到的ip就是other-ip

Bind 0.0.0.0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ ./server
$ ./server --ip 0.0.0.0
port: 27015, qlen: 0, sleep-before-recv: 0, sleep-after-listen: 0
getpeername failed, errno: 57, str: Socket is not connected
create socket fd: 3, sock_name: 0.0.0.0:0, peer_name:
getpeername failed, errno: 57, str: Socket is not connected
bind socket fd: 3, sock_name: 0.0.0.0:27015, peer_name:
getpeername failed, errno: 57, str: Socket is not connected
listen fd: 3, sock_name: 0.0.0.0:27015, peer_name:
getpeername failed, errno: 57, str: Socket is not connected
after accept sockfd: 3, sock_name: 0.0.0.0:27015, peer_name:
accepted fd: 4, sock_name: other-ip:27015, peer_name: other-ip:62771, addr: other-ip:62771
read 5 bytes from fd: 4, buf: hello
getpeername failed, errno: 57, str: Socket is not connected
after accept sockfd: 3, sock_name: 0.0.0.0:27015, peer_name:
accepted fd: 4, sock_name: 127.0.0.1:27015, peer_name: 127.0.0.1:62772, addr: 127.0.0.1:62772
read 13 bytes from fd: 4, buf: helloworldcpp
^C
1
2
3
4
5
6
7
8
9
10
11
12
13
$ ./client --ip other-ip
getpeername failed, errno: 57, str: Socket is not connected
fd: 3, sock_name: 0.0.0.0:0, peer_name:
fd: 3, sock_name: other-ip:62771, peer_name: other-ip:27015
fd: 3, send 5 bytes
fd: 3, send 5 bytes
$ ./client --ip 127.0.0.1
getpeername failed, errno: 57, str: Socket is not connected
fd: 3, sock_name: 0.0.0.0:0, peer_name:
fd: 3, sock_name: 127.0.0.1:62772, peer_name: 127.0.0.1:27015
fd: 3, send 5 bytes
fd: 3, send 5 bytes
fd: 3, send 3 bytes

Bind other-ip

1
2
3
4
5
6
7
8
9
10
11
12
13
$ ./server --ip other-ip
port: 27015, qlen: 0, sleep-before-recv: 0, sleep-after-listen: 0
getpeername failed, errno: 57, str: Socket is not connected
create socket fd: 3, sock_name: 0.0.0.0:0, peer_name:
getpeername failed, errno: 57, str: Socket is not connected
bind socket fd: 3, sock_name: other-ip:27015, peer_name:
getpeername failed, errno: 57, str: Socket is not connected
listen fd: 3, sock_name: other-ip:27015, peer_name:
getpeername failed, errno: 57, str: Socket is not connected
after accept sockfd: 3, sock_name: other-ip:27015, peer_name:
accepted fd: 4, sock_name: other-ip:27015, peer_name: other-ip:62773, addr: other-ip:62773
read 13 bytes from fd: 4, buf: helloworldcpp
^C
1
2
3
4
5
6
7
8
9
10
11
$ ./client --ip other-ip
getpeername failed, errno: 57, str: Socket is not connected
fd: 3, sock_name: 0.0.0.0:0, peer_name:
fd: 3, sock_name: other-ip:62773, peer_name: other-ip:27015
fd: 3, send 5 bytes
fd: 3, send 5 bytes
fd: 3, send 3 bytes
$ ./client --ip 127.0.0.1
getpeername failed, errno: 57, str: Socket is not connected
fd: 3, sock_name: 0.0.0.0:0, peer_name:
connect failed, errno: 61, str: Connection refused

  1. 《UNIX环境高级编程》16.3.4↩︎

  2. 《UNIX环境高级编程》16.4↩︎

  3. 《UNIX环境高级编程》16.3.4↩︎