Linux Socket 是网络编程的核心接口,它提供了进程间通信(IPC)的能力,尤其用于不同主机(通过网络)或同一主机上的进程通信。它本质上是应用层与 TCP/IP 协议栈之间的编程接口(API),遵循“一切皆文件”的哲学,操作 Socket 就像操作文件一样(使用文件描述符)。
在 Linux 环境下使用 Socket。
#include <sys/socket.h>
socket()
创建 Socket:int socket(int domain, int type, int protocol);
。
domain
domain
:参数用于指定协议族。PF_*
(Protocol Family) 和AF_*
(Address Family) 宏定义在历史上有细微区别,但在现代 Linux 系统中实际上是完全等价且可互换的。
用途 | 推荐写法 | 等价写法 | 值 |
---|---|---|---|
IPv4 通信 | AF_INET | PF_INET | 2 |
IPv6 通信 | AF_INET6 | PF_INET6 | 10 |
本地进程间通信 | AF_UNIX | PF_UNIX | 1 |
原始网络包访问 | AF_PACKET | PF_PACKET | 17 |
Netlink 通信 | AF_NETLINK | PF_NETLINK | 16 |
type
type
:指定套接字的通信类型和特性,它决定了数据传输的方式和套接字的行为。
类型 | 说明 |
---|---|
SOCK_STREAM | 流式套接字,TCP协议 |
SOCK_DGRAM | 数据报套接字,UDP协议 |
SOCK_RAW | 原始套接字,允许直接访问底层协议(如 IP、ICMP);可以自定义协议头部;需要 root 权限 (CAP_NET_RAW) |
SOCK_SEQPACKET | 有序分组套接字,结合了流式和数据报的特点;提供可靠、有序的消息传输;保留消息边界 |
Linux 特有类型标志(与基本类型组合使用)。SOCK_NONBLOCK
:非阻塞模式,创建时直接设置为非阻塞模式;替代传统的 fcntl(fd, F_SETFL, O_NONBLOCK)
。
使用方法:
int nonblock_sock = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
protocol
protocol
:用于指定要使用的具体网络协议。这个参数与 domain
和 type
一起决定了套接字的完整行为。
在大多数情况下,protocol
可以设置为 0,表示使用给定协议族和套接字类型的默认协议:
domain = AF_INET
+type = SOCK_STREAM
→ TCP (默认)domain = AF_INET
+type = SOCK_DGRAM
→ UDP (默认)domain = AF_INET6
+type = SOCK_STREAM
→ TCPv6 (默认)domain = AF_INET6
+type = SOCK_DGRAM
→ UDPv6 (默认)
特殊场景:当使用 SOCK_RAW
类型时,protocol
参数变得特别重要,它决定了:
- 套接字接收哪些类型的数据包。
- 套接字发送数据包时自动添加的协议头。
使用协议常量需要 #include <netinet/in.h>
。
协议常量 | 用途说明 |
---|---|
IPPROTO_ICMP | 发送/接收 ICMP 包 (ping, traceroute) |
IPPROTO_IGMP | 组播管理协议 |
IPPROTO_IPIP | IP-in-IP 隧道协议 |
IPPROTO_TCP | 直接处理 TCP 段 (需自行处理 TCP 头) |
IPPROTO_UDP | 直接处理 UDP 数据报 (需自行处理 UDP 头) |
IPPROTO_RAW | 发送时需要自行构造完整 IP 头 |
ETH_P_ALL | 接收所有以太网帧 (需使用 AF_PACKET domain) |
组合结果:
+-------------+---------------+-------------------------+---------------------+
| domain | type | protocol | result protocol |
+-------------+---------------+-------------------------+---------------------+
| AF_INET | SOCK_STREAM | 0 or IPPROTO_TCP | TCP |
| AF_INET | SOCK_DGRAM | 0 or IPPROTO_UDP | UDP |
| AF_INET | SOCK_RAW | IPPROTO_ICMP | ICMP |
| AF_INET | SOCK_RAW | IPPROTO_RAW | raw IP packet |
| AF_INET6 | SOCK_STREAM | 0 or IPPROTO_TCP | TCP over IPv6 |
| AF_INET6 | SOCK_DGRAM | 0 or IPPROTO_UDP | UDP over IPv6 |
| AF_INET6 | SOCK_RAW | IPPROTO_ICMPV6 | ICMPv6 |
| AF_PACKET | SOCK_RAW | ETH_P_ALL | ethernet frame |
| AF_NETLINK | SOCK_RAW | NETLINK_ROUTE | route info |
+-------------+---------------+-------------------------+---------------------+
return
返回值: 成功时返回新的 Socket 描述符(非负整数),失败返回 -1 并设置 errno
。errno
需要 #include <errno.h>
。
错误码 | 常量定义 | 含义与典型原因 |
---|---|---|
权限错误 | EACCES | 创建指定类型套接字权限不足(特别是原始套接字需要 root 权限) |
地址族不支持 | EAFNOSUPPORT | 不支持的协议族(如在不支持 IPv6 的系统上使用 AF_INET6 ) |
无效参数 | EINVAL | 无效参数组合(如无效的类型/协议组合) |
文件描述符超限 | EMFILE | 进程打开文件描述符数量已达上限(ulimit -n 查看限制) |
系统文件表满 | ENFILE | 系统打开文件总数达到内核限制(/proc/sys/fs/file-max ) |
无缓冲区空间 | ENOBUFS 或 ENOMEM | 内核内存不足,无法分配套接字资源 |
协议不支持 | EPROTONOSUPPORT | 系统不支持请求的协议或协议不可用 |
套接字类型不支持 | ESOCKTNOSUPPORT | 协议族不支持请求的套接字类型 |
协议不可用 | EPROTOTYPE | 套接字类型不支持请求的协议(如对 UDP 使用 SOCK_STREAM) |
网络子系统未运行 | ENETDOWN | 网络子系统未运行(罕见,通常表示严重系统问题) |
使用方法:
int fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (fd < 0) {
// errno 为全局变量(线程安全),需要保存以防被修改
int errsv = errno;
fprintf(stderr, "Error: %s\n", strerror(errsv));
// ...
}
// ...
bind()
绑定 socket:int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
。
- 将创建的 Socket (
sockfd
) 与一个特定的本地地址 (addr
) 和端口 (addrlen
是地址结构长度) 关联起来。 - 服务器必须调用
bind
来指定监听的端口。 - 客户端通常不需要显式调用
bind
,内核会在connect
或sendto
时自动分配一个临时端口(称为临时端口 Ephemeral Port)。
sockfd
sockfd
:套接字文件描述符,由 socket()
系统调用创建,需要有效且未绑定。
addr
addr
:指向包含绑定地址信息的结构体指针。
通用结构:
struct sockaddr {
sa_family_t sa_family; // 地址族(AF_INET, AF_INET6 等)
char sa_data[14]; // 地址数据
};
ipv4:
struct sockaddr_in {
sa_family_t sin_family; // 地址族:AF_INET
in_port_t sin_port; // 16位端口号(网络字节序)
struct in_addr sin_addr; // 32位 IPv4 地址
unsigned char sin_zero[8];// 填充字段(设为0)
};
struct in_addr {
uint32_t s_addr; // IPv4 地址(网络字节序)
};
ipv6:
struct sockaddr_in6 {
sa_family_t sin6_family; // 地址族:AF_INET6
in_port_t sin6_port; // 16位端口号(网络字节序)
uint32_t sin6_flowinfo; // IPv6 流信息
struct in6_addr sin6_addr; // 128位 IPv6 地址
uint32_t sin6_scope_id; // 作用域ID
};
struct in6_addr {
unsigned char s6_addr[16]; // IPv6 地址
};
unix:
struct sockaddr_un {
sa_family_t sun_family; // 地址族:AF_UNIX
char sun_path[108]; // 路径名
};
addrlen
addrlen
:指定 addr
结构体的实际长度。
// IPv4
sizeof(struct sockaddr_in)
// IPv6
sizeof(struct sockaddr_in6)
// UNIX 域
sizeof(struct sockaddr_un)
return
返回值: 成功时返回 0,失败返回 -1 并设置 errno
。errno
需要 #include <errno.h>
。
错误码 | 常量 | 原因与解决方案 |
---|---|---|
权限错误 | EACCES | 绑定到特权端口(<1024)需要 root 权限 解决方案:使用 sudo 或绑定到 ≥1024 的端口 |
地址已使用 | EADDRINUSE | 请求的地址(IP+端口)已被占用 解决方案:使用 SO_REUSEADDR 选项或更换端口 |
地址不可用 | EADDRNOTAVAIL | 尝试绑定到不存在的网络接口 解决方案:使用 INADDR_ANY 或有效的本地 IP |
错误文件描述符 | EBADF | sockfd 不是有效的文件描述符解决方案:检查套接字创建是否成功 |
无效参数 | EINVAL | 套接字已绑定/已连接,或地址族不匹配 解决方案:确保套接字未绑定,地址族正确 |
非套接字 | ENOTSOCK | sockfd 不是套接字描述符解决方案:检查文件描述符类型 |
内存不足 | ENOMEM | 内核内存不足 解决方案:释放系统资源或减少负载 |
只读文件系统 | EROFS | UNIX 域套接字路径在只读文件系统 解决方案:更改绑定路径 |
使用示例:
sockaddr_in addr = {
.sin_family = AF_INET,
.sin_addr = INADDR_ANY, // 绑定到所有网络接口
.sin_port = htons(8080) // 绑定到 8080 端口
};
// c++ 中
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);
addr.sin_addr.s_addr = INADDR_ANY;
if (bind(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
int errsv = errno;
fprintf(stderr, "Error: %s\n", strerror(errsv));
// ...
}
// ...
listen()
监听:int listen(int sockfd, int backlog);
。将套接字从"主动"状态转为"被动"状态,准备接受客户端连接。
sockfd
要求:
- 必须是已创建并成功绑定的套接字。
- 类型必须是
SOCK_STREAM
或SOCK_SEQPACKET
。 - 必须处于未连接状态。
backlog
指定等待连接队列的最大长度。队列满时连接将会被丢弃。
- 内核队列机制:
- 未完成连接队列 (SYN队列):
- 存储已收到客户端 SYN 但未完成三次握手的连接
- 状态:
SYN_RCVD
- 已完成连接队列 (ACCEPT队列):
- 存储已完成三次握手等待
accept()
的连接 - 状态:
ESTABLISHED
- 存储已完成三次握手等待
- 未完成连接队列 (SYN队列):
- 现代系统行为:
- Linux 4.3+ 开始使用单个队列模型
backlog
参数仅控制已完成连接队列的大小- SYN 队列大小由
/proc/sys/net/ipv4/tcp_max_syn_backlog
控制
return
返回值: 成功时返回 0,失败返回 -1 并设置 errno
。errno
需要 #include <errno.h>
。
错误码 | 常量 | 原因与解决方案 |
---|---|---|
错误描述符 | EBADF | sockfd 不是有效的文件描述符 |
非套接字 | ENOTSOCK | sockfd 不是套接字描述符 |
已连接 | EISCONN | 套接字已处于连接状态 |
操作不支持 | EOPNOTSUPP | 套接字类型不支持监听(如 UDP) |
地址已使用 | EADDRINUSE | 另一个套接字已在监听相同地址 |
无效参数 | EINVAL | 套接字未绑定或 backlog 为负 |
accept()
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
。
用于从已完成连接队列中提取第一个连接请求,创建一个新的套接字,并返回其文件描述符。
- 默认阻塞:若无连接请求,进程将阻塞等待。
- 非阻塞模式(需用
fcntl()
设置):- 若无连接,立即返回
-1
,并设errno
为EAGAIN
或EWOULDBLOCK
。
- 若无连接,立即返回
sockfd
监听套接字描述符(由 socket() 创建并通过 listen() 进入监听状态)。类型必须是 SOCK_STREAM
或 SOCK_SEQPACKET
。
addr
指向存放客户端地址信息的缓冲区(如 struct sockaddr_in
)。若不需要客户端地址,可设为 NULL
。
addrlen
- 输入时:指向的值需设为
addr
缓冲区的长度(如sizeof(struct sockaddr_in)
)。 - 输出时:内核将其修改为实际地址结构的大小。
若addr
为NULL
,此参数也应为NULL
。
return
返回值: 成功返回一个新的 sockfd(非负数),用于与客户端通信,失败返回 -1 并设置 errno
。errno
需要 #include <errno.h>
。
错误码 | 原因说明 |
---|---|
EAGAIN /EWOULDBLOCK | 非阻塞模式下无连接可用 |
EBADF | sockfd 无效 |
ECONNABORTED | 连接已中止(客户端在完成前关闭) |
EINTR | 被信号中断(可安全重启调用) |
EINVAL | 套接字未处于监听状态 |
EMFILE | 进程文件描述符耗尽 |
ENFILE | 系统文件描述符耗尽 |
EFAULT | addr 或 addrlen 指向无效地址 |
EPROTO | 协议错误(如客户端在连接建立前重置) |
connect()
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
。
用于客户端主动与服务器建立连接(TCP)或指定默认地址(UDP)。
- 对于 TCP 套接字:触发三次握手过程,建立可靠连接。
- 对于 UDP 套接字:不建立实际连接,但后续的 send()/write() 调用可省略目标地址(数据报默认发送至此地址)。
- 默认阻塞:调用后进程阻塞,直到连接成功或失败(通常最长75秒)。
- 非阻塞模式(需用
fcntl()
设置):- 立即返回
-1
,并设errno
为EINPROGRESS
(表示连接正在进行)。 - 后续需用
select()
/poll()
检查套接字是否可写(表示连接完成)。
- 立即返回
sockfd
客户端套接字描述符(通过 socket()
创建)。
addr
指向服务器地址结构(如 struct sockaddr_in
或 struct sockaddr_in6
)。必须包含服务器的 IP 地址和端口号。
addrlen
服务器地址结构的长度(如 sizeof(struct sockaddr_in)
)。
return
返回值: 成功返回 0, 失败返回 -1 并设置 errno
。errno
需要 #include <errno.h>
。
错误码 | 原因说明 |
---|---|
EINPROGRESS | 非阻塞模式下连接正在建立(需后续检查) |
EALREADY | 非阻塞套接字上已有连接尝试正在进行 |
ECONNREFUSED | 服务器拒绝连接(端口无监听) |
ETIMEDOUT | 连接超时(服务器未响应) |
ENETUNREACH | 网络不可达 |
EADDRINUSE | 本地地址已被占用 |
EINTR | 被信号中断(可安全重启调用) |
EISCONN | 套接字已处于连接状态 |
EAFNOSUPPORT | 地址族不支持(如 IPv6 地址用于 IPv4 套接字) |
EFAULT | addr 指向无效地址空间 |
recv()
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
。ssize_t read(int sockfd, void *buf, size_t count);
= ssize_t recv(int sockfd, void *buf, size_t len, 0)
从已连接的套接字接收数据(主要用于 TCP,也可用于已调用 connect()
的 UDP 套接字)。将接收到的数据存储到缓冲区 buf
中,最多接收 len
个字节。是 read()
的增强版,增加了额外的控制标志。
部分读(Partial Read):
- 常见场景:
- 接收缓冲区数据量小于请求的
count
。 - 数据包分片到达。
- 被信号中断。
- 接收缓冲区数据量小于请求的
- 必须循环读取直到获取完整数据:
// 循环接收完整消息的范式
size_t total = 0;
while (total < expected_length) {
ssize_t n = recv(sockfd, buf + total, len - total, 0);
if (n <= 0) /* 处理错误或关闭 */;
total += n;
}
数据边界:
- TCP:面向字节流,无消息边界。可能返回任意长度(1~len),需循环读取完整消息。
- UDP:面向数据报,每次调用接收一个完整数据报(若
buf
过小,多余数据被丢弃)。
阻塞行为:
- 默认阻塞:无数据时进程挂起等待。
- 非阻塞模式(
MSG_DONTWAIT
或fcntl(O_NONBLOCK)
):- 无数据时立即返回
-1
,errno=EAGAIN/EWOULDBLOCK
。
- 无数据时立即返回
带外数据:
- 使用
MSG_OOB
接收紧急数据(通常只占用 1 字节)。 - 紧急指针指向最后一个带外字节。
sockfd
已经连接的套接字描述符。
buf
数据接收缓冲区的起始位置。
len
缓冲区的最大容量(字节数)。
flags
控制接收行为的标志位(常用值见下表,可通过 |
组合)。
标志 | 值 (Linux) | 说明 |
---|---|---|
MSG_PEEK | 0x02 | 查看数据但不从接收队列移除(下次调用仍可读到相同数据) |
MSG_OOB | 0x01 | 接收带外数据(紧急数据) |
MSG_WAITALL | 0x100 | 阻塞直到请求的字节数全部接收完(除非遇到错误或连接关闭) |
MSG_DONTWAIT | 0x40 | 非阻塞操作(等价于 fcntl(O_NONBLOCK) ) |
MSG_TRUNC | 0x20 | 即使数据被截断也返回原始数据报长度(用于原始套接字) |
MSG_ERRQUEUE | 0x2000 | 从错误队列接收错误信息(用于诊断) |
return
返回值:
- 成功返回实际接收的字节数(>0)。
- 连接关闭返回 0 。
- 失败返回 -1,并设置
errno
。
错误码 | 原因说明 |
---|---|
EAGAIN /EWOULDBLOCK | 非阻塞模式下无可用数据 |
EBADF | 无效文件描述符 |
ECONNREFUSED | 远程主机拒绝连接(UDP) |
EFAULT | buf 指向无效地址空间 |
EINTR | 被信号中断(可安全重启) |
EINVAL | 无效参数(如负的 len 值) |
ENOTCONN | 套接字未连接 |
ENOTSOCK | 文件描述符不是套接字 |
ETIMEDOUT | 连接超时 |
ECONNRESET | 连接被对方重置(如对方进程崩溃) |
send()
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
。ssize_t write(int sockfd, const void *buf, size_t count);
= ssize_t send(int sockfd, const void *buf, size_t len, 0);
。
向已连接的套接字发送数据(TCP 或已连接 UDP),是 write()
的增强版,提供额外的控制标志,适用于需要特殊发送选项的场景(如带外数据、非阻塞操作等)。
阻塞行为:
- 默认阻塞:当套接字发送缓冲区满时,进程将阻塞直到有空间。
- 非阻塞模式(通过
fcntl(O_NONBLOCK)
设置):- 立即返回
-1
,errno=EAGAIN/EWOULDBLOCK
(当缓冲区满时)。 - 可能执行部分写(返回实际写入的字节数)。
- 立即返回
部分发送:
- 必须检查返回值并处理未发送数据:
size_t total_sent = 0;
while (total_sent < len) {
ssize_t n = send(sockfd, buf + total_sent, len - total_sent, flags);
if (n <= 0) /* 处理错误 */;
total_sent += n;
}
sockfd
已连接的套接字描述符。
buf
指向待发送数据的缓冲区。
len
需要发送的字节数。
flags
控制发送行为的标志位(常用值如下,可通过 |
组合)。
标志 | 值 (Linux) | 说明 |
---|---|---|
MSG_OOB | 0x01 | 发送带外数据(紧急数据) |
MSG_DONTROUTE | 0x04 | 绕过标准路由(仅限本地网络) |
MSG_DONTWAIT | 0x40 | 非阻塞操作(等价于 fcntl(O_NONBLOCK) ) |
MSG_NOSIGNAL | 0x4000 | 连接断开时不产生 SIGPIPE 信号 |
MSG_EOR | 0x80 | 标记记录结束(SOCK_SEQPACKET 类型) |
MSG_MORE | 0x8000 | 提示内核后续还有数据(优化小包合并) |
MSG_CONFIRM | 0x800 | 确认链路有效(用于 ARP 表维护) |
return
返回值: 成功返回实际发送的字节数,失败返回 -1
并设置 errno
。
错误码 | 原因说明 |
---|---|
EAGAIN /EWOULDBLOCK | 非阻塞模式下发送缓冲区满 |
EBADF | 无效文件描述符 |
ECONNRESET | 连接被对方重置(对方进程崩溃) |
EPIPE | 连接已关闭(未设置 MSG_NOSIGNAL 时会触发 SIGPIPE) |
EFAULT | buf 指向无效地址空间 |
EINTR | 被信号中断(可安全重启) |
EINVAL | 无效参数(如负的 len 值) |
ENOTCONN | 套接字未连接 |
ENOTSOCK | 文件描述符不是套接字 |
EMSGSIZE | 消息过大(UDP 数据报超过路径 MTU) |
ENOBUFS | 系统缓冲区资源耗尽 |
sendto()
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
。
UPD 数据发送。
- 必须指定目标地址 (
dest_addr
) 和地址长度 (addrlen
)。 - 如果 UDP Socket 已
connect
,也可以使用send
或write
(不需要指定目标地址)。
recvfrom()
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
。
UPD 数据接收。
- 接收数据报,并通过
src_addr
和addrlen
返回发送方的地址。 - 如果 UDP Socket 已
connect
,也可以使用recv
或read
(不会返回发送方地址)。
sendmsg()
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
。
通用数据发送:最灵活,支持分散/聚集 I/O 和辅助数据。
recvmsg()
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
。
通用数据接收:最灵活,支持分散/聚集 I/O 和辅助数据。
close()
int close(int fd);
。
头文件 #include <unistd.h>
。
- 关闭文件描述符(包括 Socket 描述符)。对于 TCP Socket,会触发四次挥手过程(如果必要)。
- 如果有多个进程共享同一个 Socket 描述符(通过
fork
),close
只是减少该描述符的引用计数。只有当引用计数为 0 时,才会真正关闭连接并释放资源。
shutdown()
int shutdown(int sockfd, int how);
。
- 提供更精细的控制,关闭连接的一个方向或两个方向。
how
:SHUT_RD
(关闭读端,不再接收数据),SHUT_WR
(关闭写端,发送 FIN 通知对方不再发送数据),SHUT_RDWR
(同时关闭读写)。- 不影响文件描述符的引用计数,无论有多少个进程持有该描述符,
shutdown
都会立即影响连接的可用性。
利用 socket 实现简易C/S。server.cpp
:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <errno.h>
#include <iostream>
#include <thread>
void handle(int fd) {
while (true) {
const size_t N = 1024;
char buf[N + 1] = {};
size_t n = recv(fd, buf, N, 0);
if (n > 0) {
buf[n] = '\0';
std::cout << "thread: " << std::this_thread::get_id() << " recv: " << buf << std::endl;
} else {
if (n == 0) {
std::cout << "thread: " << std::this_thread::get_id() << " connection closed" << std::endl;
} else {
std::cerr << "thread: " << std::this_thread::get_id() << " recv error: " << strerror(errno) << std::endl;
}
break;
}
}
}
void start_server(const char* ip, uint16_t port) {
int fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (fd < 0) {
std::cerr << "create socket error: " << strerror(errno) << std::endl;
return;
}
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
if (inet_pton(AF_INET, ip, &addr.sin_addr) != 1) {
std::cerr << "invalid ip: " << ip << std::endl;
return;
}
if (bind(fd, (sockaddr*) &addr, sizeof(addr)) < 0) {
std::cerr << "bind ip: " << ip << " error: " << strerror(errno) << std::endl;
return;
}
int backlog = 128;
if (listen(fd, backlog) < 0) {
std::cerr << "listen ip: " << ip << " error: " << strerror(errno) << std::endl;
}
while (true) {
sockaddr_in addr;
socklen_t len = sizeof(addr);
int req = accept(fd, (sockaddr*) &addr, &len);
if (req < 0) {
std::cerr << "accept error: " << strerror(errno) << std::endl;
} else {
char client_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &addr.sin_addr, client_ip, sizeof(client_ip));
std::cout << "accept " << client_ip << ":" << ntohs(addr.sin_port) << std::endl;
// create thread.
std::thread t(handle, req);
t.detach();
}
}
}
int main() {
start_server("127.0.0.1", 8080);
return 0;
}
client.cpp
:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <errno.h>
#include <iostream>
void start_client(const char*ip, uint16_t port) {
int fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (fd < 0) {
std::cerr << "create socket error: " << strerror(errno) << std::endl;
return;
}
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
if (inet_pton(AF_INET, ip, &addr.sin_addr) != 1) {
std::cerr << "invalid ip: " << ip << std::endl;
return;
}
if (connect(fd, (sockaddr*) &addr, sizeof(addr)) < 0) {
std::cerr << "connect ip: " << ip << " error: " << strerror(errno) << std::endl;
return;
}
while (true) {
const size_t N = 1024;
char buf[N];
std::cin >> buf;
send(fd, buf, strlen(buf), 0);
}
}
int main() {
start_client("127.0.0.1", 8080);
return 0;
}
Comments NOTHING