banner
NEWS LETTER

Linux 网络编程入门:Socket、TCP Server 与常用排错

Scroll down

一、网络编程先理解什么

Linux 网络编程的核心是 socket。socket 是应用程序访问网络的接口,HTTP、WebSocket、RPC 等上层协议最终都建立在 socket 之上。

入门需要先理解:

  • IP:主机地址。
  • 端口:进程提供服务的入口。
  • TCP:可靠字节流。
  • UDP:无连接数据报。
  • 服务端:监听端口,等待连接。
  • 客户端:主动连接服务端。

二、TCP 服务端流程

一个最小 TCP 服务端通常包含:

1
socket -> bind -> listen -> accept -> read/write -> close

含义:

  • socket:创建套接字。
  • bind:绑定 IP 和端口。
  • listen:开始监听。
  • accept:接受客户端连接。
  • read/write:收发数据。
  • close:关闭连接。

三、最小 TCP 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
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>

int main(void) {
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
perror("socket");
return 1;
}

int reuse = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));

struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(8080);

if (bind(server_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("bind");
close(server_fd);
return 1;
}

if (listen(server_fd, 16) < 0) {
perror("listen");
close(server_fd);
return 1;
}

printf("listen on 8080\n");

while (1) {
int client_fd = accept(server_fd, NULL, NULL);
if (client_fd < 0) {
perror("accept");
continue;
}

char buf[1024];
ssize_t n = read(client_fd, buf, sizeof(buf));
if (n > 0) {
write(client_fd, buf, n);
}

close(client_fd);
}

close(server_fd);
return 0;
}

编译:

1
gcc tcp_echo_server.c -o tcp_echo_server -Wall -Wextra -g

运行:

1
./tcp_echo_server

另开终端测试:

1
nc 127.0.0.1 8080

输入内容后回车,服务端会原样返回。

四、查看端口

查看监听端口:

1
ss -lntp

只看 8080:

1
ss -lntp | grep 8080

如果启动时报错:

1
bind: Address already in use

说明端口已经被占用。

五、TCP 客户端流程

客户端通常是:

1
socket -> connect -> write/read -> close

示例:

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
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>

int main(void) {
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) {
perror("socket");
return 1;
}

struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);
inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);

if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("connect");
close(fd);
return 1;
}

const char *msg = "hello server\n";
write(fd, msg, strlen(msg));

char buf[1024];
ssize_t n = read(fd, buf, sizeof(buf) - 1);
if (n > 0) {
buf[n] = '\0';
printf("recv: %s", buf);
}

close(fd);
return 0;
}

六、阻塞和并发

上面的服务端一次只处理一个连接。真实服务通常需要并发。

常见方式:

  • 多进程:每个连接 fork 一个子进程。
  • 多线程:每个连接创建一个线程。
  • IO 多路复用:selectpollepoll
  • 事件库:libevent、libuv、asio。

入门阶段建议先写阻塞版本,理解流程后再学习 epoll

七、常用排错工具

测试 HTTP:

1
curl -v http://127.0.0.1:8080

测试 TCP:

1
nc 127.0.0.1 8080

查看端口:

1
ss -lntp

查看进程打开的 socket:

1
lsof -p <pid>

抓包:

1
sudo tcpdump -i any port 8080

看系统调用:

1
strace -e trace=network ./tcp_echo_server

八、常见问题

8.1 Connection refused

客户端连接被拒绝,通常是服务端没启动,或者端口不对。

检查:

1
ss -lntp | grep 8080

8.2 Address already in use

端口被占用。

检查:

1
ss -lntp | grep 8080

8.3 只能本机访问

如果服务端绑定的是 127.0.0.1,外部机器无法访问。需要绑定 0.0.0.0 或指定网卡 IP。

8.4 防火墙阻断

服务启动了,但外部访问失败,可能是防火墙或云安全组限制。

九、继续学习

建议路线:

  1. 写 TCP echo server。
  2. 写 TCP echo client。
  3. 支持多客户端。
  4. 理解粘包和协议边界。
  5. 写简单 HTTP server。
  6. 学习 non-blocking socket。
  7. 学习 epoll。
  8. 阅读成熟网络库。

网络编程不是只会调用 socket API,更重要的是理解连接生命周期、错误处理和线上排错。

其他文章
目录导航 置顶
  1. 1. 一、网络编程先理解什么
  2. 2. 二、TCP 服务端流程
  3. 3. 三、最小 TCP Echo Server
  4. 4. 四、查看端口
  5. 5. 五、TCP 客户端流程
  6. 6. 六、阻塞和并发
  7. 7. 七、常用排错工具
  8. 8. 八、常见问题
    1. 8.1. 8.1 Connection refused
    2. 8.2. 8.2 Address already in use
    3. 8.3. 8.3 只能本机访问
    4. 8.4. 8.4 防火墙阻断
  9. 9. 九、继续学习
请输入关键词进行搜索