1587 字
8 分钟
网络套接字:流式套接字&TCP【Unix编程】

流式套接字(Stream Socket)用于在网络应用程序之间进行可靠的基于连接的通信。它通常使用TCP(Transmission Control Protocol)作为传输协议,因此提供了数据传输的可靠性和顺序保证。在流式套接字中,数据以字节流的形式发送和接收,双方可以确定连接的建立和断开。

使用流程#

Server端流程:#

  1. 创建套接字:使用socket(AF_INET, SOCK_STREAM, 0)创建TCP流式套接字。
  2. 绑定地址:使用bind()函数绑定服务器的IP地址和端口号。
  3. 监听连接:使用listen()函数开始监听客户端的连接请求。
  4. 接受连接:使用accept()函数接受客户端连接请求,返回一个新的套接字用于与特定客户端通信。
  5. 收发数据:使用recv()send()函数进行数据接收和发送,也可以使用标准I/O函数read()write()
  6. 关闭套接字:使用close()函数关闭新创建的通信套接字和监听套接字。

Client端流程:#

  1. 创建套接字:使用socket(AF_INET, SOCK_STREAM, 0)创建TCP流式套接字。
  2. 连接服务器:使用connect()函数连接到服务器的指定地址和端口。
  3. 收发数据:使用send()recv()函数与服务器进行数据通信,也可以使用标准I/O函数read()write()
  4. 关闭套接字:使用close()函数关闭套接字。

函数#

listen#

int listen(int sockfd, int backlog);

作用
listen 函数将由 sockfd 指定的套接字标记为被动套接字,即用于接受使用 accept(2) 到达的连接请求的套接字。

参数

  • int sockfd:引用一个类型为 SOCK_STREAMSOCK_SEQPACKET 的套接字的文件描述符。
  • int backlog:定义 sockfd 的待处理连接队列的最大长度。如果连接请求到达时队列已满,客户端可能会收到一个带有 ECONNREFUSED 指示的错误,或者如果底层协议支持重传,请求可能会被忽略,以便稍后重新尝试连接时成功。

返回值

  • 成功时返回零。
  • 失败时返回 -1,并设置 errno 以指示错误类型。

accept 和 accept4#

int accept(int sockfd, struct sockaddr *_Nullable restrict addr,
socklen_t *_Nullable restrict addrlen);
int accept4(int sockfd, struct sockaddr *_Nullable restrict addr,
socklen_t *_Nullable restrict addrlen, int flags);

作用
accept()accept4() 系统调用用于面向连接的套接字类型(如 SOCK_STREAMSOCK_SEQPACKET)。它们从监听套接字 sockfd 的待处理连接队列中提取第一个连接请求,创建一个新的已连接套接字,并返回一个指向该套接字的新文件描述符。
新创建的套接字处于非监听状态,原始套接字 sockfd 不受此调用影响。

参数

  • int sockfd:一个已经通过 socket(2) 创建、通过 bind(2) 绑定到本地地址,并在 listen(2) 后监听连接的套接字。
  • struct sockaddr *addr:指向 sockaddr 结构的指针。此结构将被填入对端套接字地址。如果 addr 为 NULL,则不填充任何内容;在这种情况下,addrlen 也不使用,应该也为 NULL。
  • socklen_t *addrlen:这是一个值-结果参数:调用者必须初始化它以包含由 addr 指向的结构的大小;返回时,它将包含对端地址的实际大小。
  • int flags:用于 accept4() 的标志位,如果 flags 为 0,则 accept4() 行为与 accept() 相同。flags 参数按位或来获得不同行为:
选项说明
SOCK_NONBLOCK在新的文件描述符所引用的打开文件描述上设置 O_NONBLOCK 文件状态标志。使用此标志可以节省额外调用 fcntl(2) 来实现相同结果的操作。
SOCK_CLOEXEC在新的文件描述符上设置 close-on-exec (FD_CLOEXEC) 标志。请参阅 open(2) 中对 O_CLOEXEC 标志的描述以了解为何这可能有用。

返回值

  • 成功时,这些系统调用返回接受套接字的文件描述符(非负整数)。
  • 错误时返回 -1,并设置 errno 指示错误类型,addrlen 保持不变。

connect#

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

作用 connect 系统调用用于将由文件描述符 sockfd 指定的套接字连接到 addr 指定的地址。addrlen 参数指定 addr 的大小。

如果套接字 sockfd 的类型是 SOCK_DGRAM,则 addr 是默认发送数据报的地址,也是接收数据报的唯一地址。如果套接字的类型是 SOCK_STREAMSOCK_SEQPACKET,则此调用尝试连接到绑定到 addr 指定地址的套接字。

参数

  • int sockfd:指定套接字的文件描述符。
  • const struct sockaddr *addr:指向套接字地址结构的指针。
  • socklen_t addrlen:指定地址结构的大小。

返回值

  • 如果连接或绑定成功,则返回零。
  • 出错时返回 -1,并设置 errno 指示错误类型。

示例#

server.c

#include "netinet/in.h"
#include "netinet/ip.h"
#include "proto.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "sys/socket.h"
#include <arpa/inet.h>
#define BUFFSIZE 1024
#define IPSIZE 24
#define SERVER_PORT "2233"
int main() {
int sd;
sd = socket(AF_INET, SOCK_STREAM, 0);
int val = 1;
if (setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val))) {
perror("setsockop()");
exit(1);
}
if (sd < 0) {
perror("socket");
exit(1);
}
struct sockaddr_in s_addr;
struct sockaddr_in c_addr;
memset(&s_addr, 0, sizeof(s_addr));
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(atoi(SERVER_PORT));
inet_pton(sd, "0.0.0.0", &s_addr.sin_addr);
socklen_t socklen = sizeof(s_addr);
socklen_t c_socklen = sizeof(c_addr);
if (bind(sd, (void *)&s_addr, socklen) < 0) {
perror("bind()");
exit(1);
}
if (listen(sd, 100) < 0) {
perror("listen");
exit(1);
}
int new_sd;
new_sd = accept(sd, &c_addr, &c_socklen);
if (new_sd < 0) {
perror("accept()");
exit(1);
}
FILE *sock_fl;
sock_fl = fdopen(new_sd, "w");
if (sock_fl == NULL) {
perror("fdopen()");
exit(1);
}
char addr[22];
inet_ntop(AF_INET, &c_addr.sin_addr, addr, c_socklen);
int port = ntohs(c_addr.sin_port);
fprintf(sock_fl, "Hello %s:%d \n", addr, port);
fclose(sock_fl);
return 0;
}

使用nc命令测试程序

Terminal window
~ nc 127.0.0.1 2233
Hello 127.0.0.1

client.c

#include "arpa/inet.h"
#include "netinet/ip.h"
#include "proto.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "sys/socket.h"
#define BUFF_SIZE 2048
#define SERVER_PORT "2233"
int main(int argc, char *argv[]) {
int sd;
sd = socket(AF_INET, SOCK_STREAM, 0);
if (sd < 0) {
perror("socket()");
exit(1);
}
struct sockaddr_in s_addr;
s_addr.sin_family = AF_INET;
inet_pton(AF_INET, "0.0.0.0", &s_addr.sin_addr);
s_addr.sin_port = htons(atoi(SERVER_PORT));
socklen_t socklen = sizeof(s_addr);
if (connect(sd, &s_addr, socklen) < 0) {
perror("connect()");
exit(1);
}
FILE *sock_fl;
sock_fl = fdopen(sd, "r");
if (sock_fl == NULL) {
perror("sock_fl()");
exit(1);
}
char buf[BUFF_SIZE];
int cnt;
while ((cnt = fread(buf, 1, BUFF_SIZE, sock_fl)) > 0) {
// 使用 fwrite 将读取到的 cnt 字节直接写入标准输出
fwrite(buf, 1, cnt, stdout);
}
fclose(sock_fl);
return 0;
}

使用nc命令测试程序,client会打印file中的内容。

Terminal window
nc -l -p 2233 < file

补充#

命令nc#

Ncat 是一个功能丰富的网络实用程序,可以从命令行跨网络读取和写入数据。

Terminal window
nc [选项] [目标主机] [端口]

主要功能:
客户端

Terminal window
nc 192.168.1.1 1234

端口扫描

Terminal window
nc -zv 192.168.1.1 80-100

监听端口

Terminal window
nc -l -p 1234

服务端传送文件

Terminal window
nc -l -p 1234 < file.txt
网络套接字:流式套接字&TCP【Unix编程】
https://milkfunc.top/posts/unix环境高级编程学习摘要/网络套接字/网络套接字流式套接字/
作者
CapCake
发布于
2025-08-21
许可协议
CC BY-NC-SA 4.0