本文主要是在Linux环境下编写socket程序,本次实现简单TCPserverclient以及echo serverecho client

什么是Socket

Socket中文译名套接字, 是一个当时没有在计网中讲到的知识, 但确实是极其重要的一个知识点, 他是基于TCP/IP协议的封装抽象层, 它对于TCP协议进行了封装, 使得其现在已经成为了最为通用的网络通信接口/应用程序接口.
关于TCP协议可以看下面这篇文章

https://tools.ietf.org/html/rfc793

为什么会有Socket

我们知道, 服务/服务器其实是有很大可能不存在于一个计算机上的, 这就有了客户端与服务端, 在服务器之间, 也是需要存在连接通信的, 所以需要有一个用于进行通信的通用 可编程 接口, 这就是socket, 他对于 serverclient 之间架起了一道桥, 让 serverport 绑定, 让 clientip:port 连接, 这样就相当于 client 进程与 server 进程之间有了像是管道一样的通信, 不过这个通信是全双工的.

img
img

我们回顾TCP状态转换图

img
img

在这里能够找到许多socket封装的影子, 从客户端来看

img
img

则更加明显

学习Linux 下的网络编程(socket编程)

创建套接字

int socket(int domain, int type, int protocol)
return : 文件描述符 / or not -1
domain: 套接字所使用的协议簇
type: 套接字数据传输类型
protocol: 套接字所使用的协议

文件描述符(windows下称之为句柄):

系统分配给文件或套接字的一个整数:

img
img

https://segmentfault.com/a/1190000009724931

img
img
协议簇:
img
img
套接字类型:
  1. SOCK_STREAM(流式)(面向连接)
    img
    img
  2. SOCK_DGRAM(包式)(面向消息)
    img
    img
协议
img
img

sockaddr_in

img
img
img
img

网络字节序

img
img

IntelCPU默认都是小端序, 而网络连接统一都是大端序

img
img
img
img

TCPserver/client

img
img
img
img

所以又回到了最最上面的第一个图:

img
img
accept()
img
img

sock: 利用socket()函数建立的sock
addr: 地址变量
addlen: 结构体长度

connect()
img
img

实现简单的server/client

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
//
// Created by xmmmmmovo on 2020/4/16.
//
#include <arpa/inet.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <sys/socket.h>
#include <unistd.h>

using std::string;
using std::to_string;

void error_handler(char *message);

int main(int argc, char *argv[]) {
int serv_sock, clnt_sock;

struct sockaddr_in serv_addr {};
struct sockaddr_in clnt_addr {};
socklen_t clnt_addr_size;

string message = "Hello World!";

if (argc != 2) {
printf("Usage : %s <port>\n", argv[0]);
exit(1);
}

serv_sock = socket(PF_INET, SOCK_STREAM, 0);
if (serv_sock == -1) {
error_handler("socket() error");
}

memset(&serv_addr, 0, sizeof(serv_addr));// 初始化内存

serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);// 当前主机ip
serv_addr.sin_port = htons(atoi(argv[1]));
if (bind(serv_sock, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) == -1) {
error_handler("bind() error");
}

if (listen(serv_sock, 5) == -1) {
error_handler("listen() error");
}

clnt_addr_size = sizeof(clnt_addr);
clnt_sock = accept(serv_sock, (struct sockaddr *) &clnt_addr, &clnt_addr_size);
if (clnt_sock == -1) {
error_handler("accept() error");
}

for (int i = 1; i < 4; ++i) {
sleep(rand() % 2);
string tmp = message + to_string(i);
write(clnt_sock, tmp.c_str(), tmp.size());
}

// write(clnt_sock, message.c_str(), message.size());// 连接之后直接向client写入数据
close(clnt_sock);
close(serv_sock);
return 0;
}

void error_handler(char *message) {
fputs(message, stderr);
fputc('\n', stderr);
exit(0);
}

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
//
// Created by xmmmmmovo on 2020/4/16.
//
#include <arpa/inet.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <sys/socket.h>
#include <unistd.h>

using std::string;

void error_handler(char *messgae);

int main(int argc, char *argv[]) {
int sock;
sockaddr_in serv_addr{};
int str_len = 0;
int idx = 0, read_len = 0;
string message;// 信息流
message.resize(100, '\0');

if (argc != 3) {
printf("Usage : %s <IP> <port>\n", argv[0]);
exit(1);
}

sock = socket(PF_INET, SOCK_STREAM, 0);
if (sock == -1) {
error_handler("socket() error");
}


memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
// 这里会自动将ip字符串转换为uint32_t类型IP数字
serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
serv_addr.sin_port = htons(atoi(argv[2]));

if (connect(sock, (sockaddr *) &serv_addr, sizeof(serv_addr)) == -1) {
error_handler("connect() error!");
}
bool check = false;

// 每次只读出一个字节
while (read_len = read(sock, &message[idx++], 1)) {
if (str_len == -1) {
if (check)
error_handler("read() error!");
else
sleep(3);
}

str_len += read_len;
}

printf("Message from server is : %s \n", message.c_str());
printf("Function read call count : %d \n", str_len);
close(sock);
return 0;
}

void error_handler(char *message) {
fputs(message, stderr);
fputc('\n', stderr);
exit(0);
}

结果

可以看到 client 成功接受到了流字符:

img
img

同时 server 也发送成功结束了程序:

img
img

实现echo服务器

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
//
// Created by xmmmmmovo on 2020/4/16.
//
#include <arpa/inet.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <sys/socket.h>
#include <unistd.h>

using std::stoi;
using std::string;
using std::to_string;

constexpr int BUF_SIZE = 1024;

void error_handler(char *message);

int main(int argc, char *argv[]) {
int serv_sock, clnt_sock;
char message[BUF_SIZE];
int size_len = 4;
char size_buf[size_len];
sockaddr_in serv_addr{}, clnt_addr{};
socklen_t clnt_addr_size;
int str_len = 0;

if (argc != 2) {
printf("Usage : %s <port>\n", argv[0]);
exit(1);
}

serv_sock = socket(PF_INET, SOCK_STREAM, 0);
if (serv_sock == -1) {
error_handler("socket() error");
}

memset(&serv_addr, 0, sizeof(serv_addr));// 初始化内存

serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);// 当前主机ip
serv_addr.sin_port = htons(atoi(argv[1]));
if (bind(serv_sock, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) == -1) {
error_handler("bind() error");
}

if (listen(serv_sock, 5) == -1) {
error_handler("listen() error");
}

clnt_addr_size = sizeof(clnt_addr);// 算长度

///主体
for (int i = 0; i < 5; ++i) {
clnt_sock = accept(serv_sock, (sockaddr *) &clnt_addr, &clnt_addr_size);
if (clnt_sock == 1) {
error_handler("accept() error!");
} else {
printf("Connected client %d \n", i + 1);
}


while (true) {
if ((str_len = read(clnt_sock, size_buf, size_len)) != 0) {
if ((str_len = read(clnt_sock, message, stoi(size_buf))) != 0) {
write(clnt_sock, size_buf, size_len);
write(clnt_sock, message, str_len);
} else {
break;
}
} else {
break;
}
}

close(clnt_sock);
}
///

close(serv_sock);
return 0;
}

void error_handler(char *message) {
fputs(message, stderr);
fputc('\n', stderr);
exit(0);
}

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
//
// Created by xmmmmmovo on 2020/4/16.
//
#include <arpa/inet.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <sys/socket.h>
#include <unistd.h>

using std::stoi;
using std::string;
using std::to_string;

constexpr int BUF_SIZE = 1024;

void error_handler(char *messgae);

int main(int argc, char *argv[]) {
int sock;
sockaddr_in serv_addr{};
int str_len = 0;
char message[BUF_SIZE];

if (argc != 3) {
printf("Usage : %s <IP> <port>\n", argv[0]);
exit(1);
}

sock = socket(PF_INET, SOCK_STREAM, 0);
if (sock == -1) {
error_handler("socket() error");
}


memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
// 这里会自动将ip字符串转换为uint32_t类型IP数字
serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
serv_addr.sin_port = htons(atoi(argv[2]));

if (connect(sock, (sockaddr *) &serv_addr, sizeof(serv_addr)) == -1) {
error_handler("connect() error!");
} else {
puts("Connected......");
}

while (true) {
fputs("Input message(Q/q to quit): ", stdout);
std::fgets(message, BUF_SIZE, stdin);

if (!strcmp(message, "q\n") || !strcmp(message, "Q\n")) {
break;
}

int l = strlen(message);
printf("len = %d\n", l);
string s = to_string(l);
s.resize(4);
write(sock, s.c_str(), 4);
write(sock, message, strlen(message));
char buf[4];
str_len = read(sock, buf, 4);
str_len = read(sock, message, stoi(buf));
message[str_len] = 0;
printf("Message from server: %s\n", message);
}


close(sock);
return 0;
}

void error_handler(char *message) {
fputs(message, stderr);
fputc('\n', stderr);
exit(0);
}

结果

client:

img
img

server:

img
img