当前位置: 首页 > news >正文

Linux 网络编程套接字

Linux 网络编程套接字

13.1 背景概念

13.1.1 源IP地址与目标IP地址理解

在IP网络通信中,IP数据包是实现主机间通信的基本单元。在每一个IP数据包的首部(Header)中,包含两个关键字段:源IP地址(Source IP Address) 和 目标IP地址(Destination IP Address),它们共同标识了数据包的发送方和接收方。

源IP地址指的是数据包最初发送方的IP地址,即该数据包是从哪个主机发出的。目标IP地址指的是数据包的接收方IP地址,即该数据包希望最终到达的目的主机地址。

这两个地址在整个数据传输过程中起着定位和路由的核心作用。路由器在转发数据包时,会依据目标IP地址决定下一跳的转发路径,确保数据包能够准确送达目标主机。而当目标主机处理完请求并回传响应时,会构造新的数据包,其中源IP和目标IP地址将与原始请求包的对应字段互换。

13.1.2端口号理解

IP地址+定位到主机,端口号定位到主机里的程序

举例:

打开微信和网页浏览器,它们都需要网络,但你电脑的 IP 地址是一样的,那数据怎么知道该给谁?

靠的就是端口号。微信用了端口号比如:5200;浏览器用了端口号比如:8080;数据包中除了有 IP 地址,还有端口号,确保:

收到的是网页的就发给浏览器,收到的是消息的就发给微信。

在 TCP/IP 协议中,端口号(Port Number 是一个 16 位的无符号整数,范围为 0 ~ 65535。用于标识某台主机中运行的具体网络应用程序(或者进程)。

每个网络连接由以下四个元素唯一确定,称为四元组

IP地址、源端口号、目标IP地址、目标端口号

端口号的分类:

端口范围

类型

用途举例

0 ~ 1023

知名端口

HTTP 80HTTPS 443FTP 21

1024 ~ 49151

注册端口

用于注册服务,如数据库 MySQL 默认端口 3306

49152 ~ 65535

动态端口 / 私有端口

临时通信时操作系统随机分配

示例:

你访问一个网站:浏览器本地使用一个随机端口(比如:52634);请求发往服务器的 IP地址 + 端口80(HTTP服务);数据返回时服务器发给你:目标IP = 你,目标端口 = 52634。

13.1.3 TCP协议

TCP 是传输层协议,位于 IP 协议之上,它的主要作用是:实现两台主机之间可靠、有序的数据通信。

如果 IP 只是把“数据包”从 A 送到 B,TCP 就保证:

数据完整、不丢包;顺序正确;收到就一定会确认;不会重复、不乱序。

TCP 的核心特点详解:

传输层协议:TCP 处在 OSI 七层模型中的第四层(传输层)。它不关心数据的内容,也不管数据怎么显示,只负责把数据可靠、安全地送到对方程序。TCP 通过使用端口号,和应用层通信。

面向连接:

TCP 是面向连接的协议,发送数据前必须先建立连接(就像打电话前要先拨号)。建立连接过程:三次握手(Three-Way Handshake);断开连接过程:四次挥手(Four-Way Handshake);建立连接后,两台主机会知道彼此的状态,并做好收发数据的准备。

可靠传输:

TCP 使用多种机制来确保数据传输可靠

机制

作用说明

确认应答 ACK

每收到一段数据就要回复确认(ACK)

超时重传

如果没收到 ACK,就重新发送那段数据

序号(Sequence)

数据有编号,保证顺序正确

滑动窗口

控制数据流速,避免网络拥堵

拥塞控制

根据网络状况自动调整发送速度

面向字节流:

TCP 把数据看成一个无结构的字节流,应用程序发送的数据被当作字节序列,TCP 会将它划分为合适的数据段进行传输。

举例来说,如果你用 TCP 发送了“hello world”,TCP 不会按“单词”或“句子”去看,而是:

h e l l o   w o r l d

然后将它拆成多个数据包发送,对方收到后再按顺序拼接

举例:

想象你给朋友邮寄一份书稿:你先打电话确认他在家(建立连接);把稿子拆成一页页放进信封编号(序号);每寄一封就等他回信告诉你“收到了”(ACK);如果过一阵没收到回信,就重寄(超时重传);他收到后按编号顺序整理成完整书稿(有序)。

13.1.4 UDP协议

UDP(用户数据报协议)是一个轻量级的传输层协议,它和 TCP 一样,也建立在 IP 协议之上。但与 TCP 不同的是:UDP 提供的是无连接、不可靠、面向数据报的传输服务;它牺牲了一些可靠性,换来了更高的效率和更少的延迟。

UDP 的核心特性详解:

传输层协议

UDP 也属于 传输层协议,与 TCP 处于同一层;它的主要作用是:为应用层提供简单、快速的数据传输服务;UDP 使用端口号来标识应用程序(同 TCP 一样)。

无连接

UDP 在发送数据前不需要建立连接,直接发送;就像发短信,写好就发,不管对方有没有准备好接收;优点是:速度快,开销小;缺点是:不能保证对方收到,也不知道对方状态

不可靠传输

UDP 不保证数据一定送达,也不确认是否送达,它只管发。

结果是:

速度快;可能丢包、乱序、重复;但有些应用可以容忍这些问题,比如:

视频会议(丢几帧没关系)、语音通话(不卡顿比准确更重要)、实时游戏(延迟更关键)

面向数据报

UDP 把应用层传来的数据称为数据报(Datagram),直接打包发送,每个数据报都是一个完整的独立单位。不像 TCP 把数据当作连续的字节流,UDP 一次发一个“整体”。

13.1.5 网络字节序

网络字节序是网络编程中必须理解的概念,它和计算机本地的大小端存储密切相关,是数据在不同主机之间准确传递的保障机制。

什么是字节序?

字节序就是多字节数据在内存中如何排列

例如:0x12345678 是一个 4 字节整数(32 位),用十六进制表示。

大端字节序(Big Endian):

高位字节排在前(低地址)

存储顺序为:

地址:0x00 0x01 0x02 0x03

数据:0x12 0x34 0x56 0x78

小端字节序(Little Endian):

低位字节排在前(低地址)

存储顺序为:

地址:0x00 0x01 0x02 0x03

数据:0x78 0x56 0x34 0x12

注:

高字节(high byte):数值上更“靠左”的那个字节,比如上面的 0x12

低字节(low byte):数值上更“靠右”的那个字节,比如上面的 0x78

不同的 CPU 架构有不同的字节序:

x86(Intel/AMD)是小端

有些嵌入式系统(如 ARM)支持大/小端可切换

网络字节序

网络字节序就是大端字节序,即:高位字节先发送,低位字节后发送

这是 TCP/IP 协议强制规定的统一标准,确保不同主机之间可以准确地理解彼此传输的数值。

网络字节序就是大端字节序,小端机也会在“接收时”先把收到的大端序数据转回自己习惯的小端序,这依靠两个经典函数来完成。

#include<arpa/inet.h>

// 主机序转网络序

uint32_t htonl(uint32_t hostlong);  //

//将一个 32 位的主机字节序整数转换为网络字节序。

//参数:hostlong 是一个 4 字节整数(如 IP 地址 192.168.1.1)

//返回值:转换为大端的 4 字节整数

uint16_t htons(uint16_t hostshort);

//将一个 16 位主机字节序整数转换为网络字节序。

//参数:hostshort 是一个 2 字节整数(如端口号)

//返回值:转换为大端的 2 字节整数

// 网络序转主机序

uint32_t ntohl(uint32_t netlong);  

//将一个 32 位的网络字节序整数转换为主机字节序。

//参数:netlong 是一个网络字节序的整数

//返回值:转换为本机的字节序(如果你是小端机,就变成小端)

uint16_t ntohs(uint16_t netshort);

//将一个 16 位的网络字节序整数转换为主机字节序。

//参数:netshort 是一个网络字节序的短整数

//返回值:转换为主机字节序

16位(short 是两个字节的数据

32位(long 是四个字节的数据

13.2 socket编程接口

Socket(套接字)就是网络编程里的“插口”,让你的程序可以通过网络发送或接收数据, 在编程层面,Socket 是操作系统提供的网络通信接口。

Socket 编程的两种角色:

客户端(Client):主动发起连接

服务器(Server):被动等待连接

Socket 可以用在不同的协议上:

TCP Socket:可靠的、面向连接的(类似打电话)

UDP Socket:不可靠的、无连接的(类似发短信)

13.2.1 socket 常见API

int socket(int domain, int type, int protocol) (TCP/UDP, 客户端 + 服务器)

作用:创建一个套接字,返回套接字文件描述符

参数说明:

domain:协议族(地址族)

AF_INET:IPv4

AF_INET6:IPv6

AF_UNIX:本地进程间通信

type:套接字类型

SOCK_STREAM:面向连接的(TCP)

SOCK_DGRAM:无连接的(UDP)

protocol:协议,一般填 0(让系统自动选 TCP 或 UDP)

返回值:

成功:返回一个非负整数(socket 文件描述符)

失败:返回 -1

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

(TCP/UDP, 服务器)

作用: 将套接字与 IP 地址和端口号绑定。

参数说明:

sockfd:由 socket() 创建的套接字

addr:指向 sockaddr_in 结构体的指针(需强转)

addrlen:地址结构体大小(sizeof(struct sockaddr_in))

返回值:

成功:0

失败:-1

int listen(int sockfd, int backlog); (TCP, 服务器)

作用: 把套接字设为被动监听状态,准备接受连接。

参数说明:

sockfd:由 socket() 创建的套接字

backlog:连接队列的最大长度(一般设成 5 或更大)

返回值:

成功:0

失败:-1

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); (TCP, 服务器)

作用: 接收连接请求,返回一个新的 socket 用于通信。

参数说明:

sockfd:监听套接字(由 listen() 设置)

addr:用于接收客户端地址信息

addrlen:传入传出参数,指定 addr 的长度

返回值:

成功:新的 socket 文件描述符(用于与客户端通信)

失败:-1

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

(TCP, 客户端)

作用: 客户端向服务端发起连接请求。

参数说明:

sockfd:由 socket() 创建的套接字

addr:服务端地址信息(sockaddr_in 强转)

addrlen:地址长度

返回值:

成功:0

失败:-1

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

( TCP, 客户端和服务器端)

作用: 发送数据

参数说明:

sockfd:通信 socket(不是监听 socket)

buf:要发送的数据指针

len:数据长度

flags:通常设为 0

返回值:

成功:返回实际发送的字节数

失败:-1

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

( TCP, 客户端和服务器端)

作用: 接收数据,对应send

参数说明:

sockfd:通信 socket

buf:接收缓冲区

len:缓冲区大小

flags:通常设为 0

返回值:

成功:接收到的字节数,0 表示连接关闭

失败:-1

int close(int sockfd);

(TCP/UDP, 客户端 + 服务器)

作用: 关闭 socket 连接,释放资源。

参数说明:

sockfd:要关闭的套接字描述符

返回值:

成功:0

失败:-1

ssize_t recvfrom(int sockfd, void *buf, size_t len,int flags, struct sockaddr *src_addr, socklen_t *addrlen);(UDP, 接收数据,还能获取对方的 IP 和端口信息)

参数

sockfd  用 socket() 创建的 socket 文件描述符

buf 接收数据的缓冲区

len buf 的大小

flags     一般用 0(可选其他:MSG_PEEK, MSG_DONTWAIT 等)

src_addr      用于保存 发送方地址信息(可以是 sockaddr_in 强转过来的)

addrlen 传入传出参数,表示地址结构体大小

返回值

正常:返回接收到的字节数

失败:返回 -1,并设置 errno

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

参数:

sockfd  用 socket() 创建的 socket 文件描述符

buf 要发送的数据(指针)

len 要发送数据的长度

flags     设置为 0 通常就够了(可选 MSG_CONFIRM 等)

dest_addr    指定目标主机的地址(sockaddr_in 类型强转)

addrlen dest_addr 的大小,一般是 sizeof(sockaddr_in)

ssize_t read(int fd, void *buf, size_t count);

参数

fd   文件描述符,通常是 socket 返回的值

buf 缓冲区,用于存放读到的数据

count    最多读多少字节

返回值 实际读取的字节数(0 表示连接关闭,-1 表示出错)

ssize_t write(int fd, const void *buf, size_t count);

参数

fd   文件描述符,通常是 socket

buf 要发送的数据内容

count    要发送的数据字节数

返回值 实际写出的字节数(-1 表示出错)

Socket 编程中,TCP 和 UDP 使用的是同一套 API 接口函数,但根据协议的不同,使用方式略有区别

功能

TCP(面向连接)

UDP(无连接)

创建套接字

socket(AF_INET, SOCK_STREAM, 0)

socket(AF_INET, SOCK_DGRAM, 0)

绑定地址

bind()

bind()(服务器使用)

建立连接

connect()(客户端)

不需要(可选)

监听连接

listen()(服务器)

不需要

接受连接

accept()(服务器)

不需要

发送数据

send() / write()

sendto() or send()

接收数据

recv() / read()

recvfrom() or recv()

主动关闭连接

close()

close()

UDP connect() 只是指定默认目标地址,省得每次写 sendto()。但不建立真正连接。

TCP API 使用流程(面向连接)

适合:数据量大、可靠性高的通信,如网页访问、文件传输

客户端:

socket();

connect();

send()/recv();

close();

服务器端

socket();

bind();

listen();

accept();

send()/recv();

close();

UDP API 使用流程(无连接):

适合:快速、简单、低延迟通信,如 DNS、视频通话

客户端

socket();

sendto(); // 发送数据到某个IP:端口

recvfrom(); // 接收对方数据

close();

服务器端

socket();

bind();

recvfrom();

sendto();

close();

13.2.2 sockaddr结构

struct sockaddr 是 Socket API 中用于不同协议族(IPv4、IPv6、UNIX 域套接字等)地址信息的统一表示形式,sockaddr 是所有 socket 地址结构体的通用表示形式。

struct sockaddr 是一个通用结构体,用于函数参数中,比如 bind()、connect()、accept() 要求的是:

const struct sockaddr *addr

但实际上传入的是更具体的结构体,比如:

struct sockaddr_in(用于 IPv4)

struct sockaddr_in6(用于 IPv6)

struct sockaddr_un(用于本地通信)

所以我们要强制类型转换:

(struct sockaddr *)&addr_in

sockaddr 定义如下(来自 <sys/socket.h>)

struct sockaddr {

    sa_family_t sa_family;    // 地址族(如 AF_INET)

    char sa_data[14];  // 地址数据(具体内容依赖实际类型)

};

实际开发中我们几乎不用它来存数据,而是用专门的结构体,例如:

IPv4 专用结构体:struct sockaddr_in

#include <netinet/in.h>

struct sockaddr_in {

    sa_family_t   sin_family;  // 地址族,必须是 AF_INET

    uint16_t   sin_port;    // 端口号(网络字节序,需 htons)

    struct in_addr sin_addr;    // IPv4 地址(32位,网络字节序)

    char  sin_zero[8]; // 填充,使大小与 sockaddr 一致

};

In_addr结构体

struct in_addr {

    in_addr_t s_addr;  // 32 位 IP 地址(网络字节序)

};

它就一个成员:s_addr,类型是 in_addr_t,本质上是个 uint32_t(无符号 32 位整数)

s_addr 存的是 网络字节序 的 IP 地址

常见用法:

struct sockaddr_in sin;

sin.sin_family = AF_INET;

sin.sin_port = htons(12345);

sin.sin_addr.s_addr = inet_addr("127.0.0.1");  // 点分十进制转网络字节序

(struct sockaddr *)&sin

网络编程中的地址转换函数主要用于 字符串地址(如 "127.0.0.1") 和二进制地址(如 in_addr 结构体)之间的相互转换,它们常用于设置 socket 地址和打印对端地址。

常用的地址转换函数一览

函数名

用途

头文件

inet_pton

点分十进制 → 二进制地址

<arpa/inet.h>

inet_ntop

二进制地址 → 点分十进制

<arpa/inet.h>

inet_addr

字符串 in_addr_t(过时)

<arpa/inet.h>

inet_ntoa

in_addr → 字符串(过时)

<arpa/inet.h>

inet_pton点分十进制 → 二进制地址

int inet_pton(int af, const char *src, void *dst);

参数:

af:地址族(AF_INET 或 AF_INET6)

src:点分十进制的 IP 地址字符串,如 "192.168.1.1"

dst:输出地址的指针(如 &sin_addr)

返回值:

1     转换成功,src 是合法的 IP 地址字符串

0     转换失败,src 是 无效的地址格式(例如拼错、写错)

-1    转换失败,af 参数(地址族) 无效,且 errno 会被设置

示例:

struct sockaddr_in addr;

inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);

inet_ntop:二进制地址 → 点分十进制

const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

参数:

af:地址族(AF_INET 或 AF_INET6)

src:二进制格式的地址

dst:目标缓冲区,存储转换后的字符串地址

size:dst 缓冲区大小

返回值

非 NULL 指针 转换成功,返回的就是 dst 缓冲区的地址(同一个指针)

NULL    转换失败,通常是 af 不合法 或 dst 太小,且 errno 被设置

示例:

char ip_str[INET_ADDRSTRLEN];

inet_ntop(AF_INET, &addr.sin_addr, ip_str, INET_ADDRSTRLEN);

printf("IP地址是:%s\n", ip_str);

//INET_ADDRSTRLEN IPv4 字符串地址的最大长度(包含末尾的 \0 终止符)是 16 字节

13.2.3 TCP通信的完整流程

Socket 初始化阶段(TCP连接尚未建立)三次握手

客户端:

fd = socket()

创建 socket 文件描述符,准备进行 TCP 连接。

connect(fd, 服务器地址端口)

向服务器发起连接请求(发送 SYN 报文),状态变为 SYN_SENT。

等待服务器响应,接收到 SYN+ACK 后,发送 ACK,状态变为 ESTABLISHED。

服务器:

listenfd = socket()

创建监听 socket。

bind(listenfd, 地址端口)

绑定 socket 到指定 IP 和端口。

listen(listenfd, 队列长度)

把 socket 变成监听状态,状态变为 LISTEN。

connfd = accept(listenfd)

阻塞等待客户端连接请求,收到后状态变为 SYN_RCVD。

接收客户端 ACK,状态变为 ESTABLISHED,分配一个新的 connfd 与客户端通信。

三次握手:

客户端 → 服务端:SYN

客户端发送一个SYN(同步)报文,表示希望建立连接。

此时客户端进入 SYN_SEND 状态。

服务端 → 客户端:SYN + ACK

服务端收到SYN后,回复一个带有 SYN 和 ACK 标志位的报文。

表示收到请求,并同意建立连接。

服务端进入 SYN_RECEIVED 状态。

客户端 → 服务端:ACK

客户端收到 SYN+ACK 后,回复一个 ACK 报文。

表示连接建立完成。

客户端进入 ESTABLISHED 状态,服务端也进入 ESTABLISHED 状态。

数据通信阶段

双方都处于 ESTABLISHED 状态后,可以开始双向通信:

客户端:

write(fd, buf, size):发送数据(会触发 TCP 的发送机制)。

read(fd, buf, size):阻塞等待接收数据。

通常是循环多次进行。

服务器:

read(connfd, buf, size):读取客户端请求(阻塞)。

write(connfd, buf, size):写入响应数据。

同样是循环进行。

连接终止阶段 四次挥手

客户端主动关闭连接:

close(fd):

发出 FIN 报文,状态变为 FIN_WAIT_1。

收到服务器的 ACK,进入 FIN_WAIT_2 状态。

等待服务器的 FIN 报文,收到后发送 ACK,进入 TIME_WAIT。

等待一段时间后进入 CLOSED。

服务器端:

收到 FIN 后返回 ACK,状态变为 CLOSE_WAIT。

等待处理完剩下的数据后,调用 close(connfd),发送 FIN,状态变为 LAST_ACK。

等待客户端 ACK,收到后进入 CLOSED 状态。

13.3 示例

13.3.1 UDP示例

服务器

#include <sys/types.h>

#include <sys/socket.h>

#include <iostream>

#include <string>

#include <stdio.h>

#include <netinet/in.h>

#include <unistd.h>

#include <string.h>

#include <arpa/inet.h>

int main()

{

    // 创建一个 UDP 套接字

    // AF_INET 表示 IPv4,SOCK_DGRAM 表示 UDP,0 表示默认协议

    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);

    if (sockfd == -1)

    {

        perror("创建套接字失败!");

        return 1;

    }

    // 定义本地地址结构体 sin 和客户端地址结构体 clr

    sockaddr_in sin, clr;

    sin.sin_family = AF_INET;             // 使用 IPv4

    sin.sin_addr.s_addr = INADDR_ANY;     // 接收任意地址发来的数据

    sin.sin_port = htons(12345);          // 设置本地监听端口为 12345(需要用 htons 转换字节序)

    // 将套接字与本地地址绑定

    if (bind(sockfd, (const struct sockaddr *)&sin, sizeof(sin)) < 0)

    {

        perror("绑定失败!");

        close(sockfd);

        return 1;

    }

    // 准备接收数据

    char buf[1024];                    // 接收缓冲区

    socklen_t addl = sizeof(clr);     // 用于接收客户端地址长度

    // 接收客户端发来的数据

    // 这里第三个参数 -1 是错误的,应为 sizeof(buf) - 1,避免越界

    ssize_t n = recvfrom(sockfd, buf, sizeof(buf) - 1, 0, (sockaddr *)&clr, &addl);

    if (n == -1)

    {

        perror("接收数据失败!");

        close(sockfd);

        return 1;

    }

    buf[n] = '\0';  // 添加字符串结束符,确保安全打印

    std::cout << "收到消息:" << buf << std::endl;

    std::cout << "来自:" << inet_ntoa(clr.sin_addr)

              << ":" << ntohs(clr.sin_port) << std::endl;

    // 关闭 socket

    close(sockfd);

    return 0;

}

客户端

#include <sys/types.h>

#include <sys/socket.h>

#include <iostream>

#include <string>

#include <stdio.h>

#include <netinet/in.h>

#include <unistd.h>

#include <string.h>

#include <arpa/inet.h>

int main()

{

    // 创建一个 UDP 套接字(socket)

    // 参数 AF_INET 表示使用 IPv4 协议

    // 参数 SOCK_DGRAM 表示使用 UDP 协议

    // 参数 0 表示让系统自动选择协议(对于 UDP 来说就是 IPPROTO_UDP)

    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);

    if (sockfd == -1)

    {

        perror("创建套接字失败!");

        return 1;

    }

    // 创建服务器的地址信息结构体

    sockaddr_in ser;

    ser.sin_family = AF_INET;               // 协议族 IPv4

    ser.sin_port = htons(12345);            // 服务器端口号,使用 htons 转为网络字节序

    ser.sin_addr.s_addr = inet_addr("127.0.0.1"); // 服务器 IP 地址(这里是本机回环地址)

    // 要发送的消息内容

    const char *msg = "Hello from UDP client!";

    // 发送数据给服务器(UDP 不需要建立连接)

    // 参数依次为:套接字、发送缓冲区、长度、标志、目标地址结构体、地址长度

    sendto(sockfd, msg, strlen(msg), 0,

           (sockaddr *)&ser, sizeof(ser));

    // 接收服务器返回的数据

    char buffer[1024];                 // 接收缓冲区

    socklen_t len = sizeof(ser);      // 地址结构体长度,用于 recvfrom 的最后一个参数

    int n = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0,

                     (sockaddr *)&ser, &len); // 接收数据,存入 buffer

    if (n < 0)

    {

        perror("接受失败");

    }

    else

    {

        buffer[n] = '\0';  // 手动添加字符串结束符

        std::cout << "从服务器接受: " << buffer << std::endl;

    }

    // 关闭套接字,释放资源

    close(sockfd);

    return 0;

}

Makefile

all:client server

client:client.cpp

    g++ -o client client.cpp

server:server.cpp

    g++ -o server server.cpp

.PHONY:clean

clean:

    rm -f client server

13.3.2 TCP示例

服务器

#include <sys/types.h>

#include <sys/socket.h>

#include <iostream>

#include <stdio.h>

#include <netinet/in.h>

#include <unistd.h>

#include <string.h>

#include <arpa/inet.h>

int main()

{

    // 创建 TCP 套接字

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);

    if (sockfd < 0)

    {

        perror("创建套接字失败!");

        return 1;

    }

    // 配置服务器地址

    sockaddr_in sin;

    sin.sin_family = AF_INET;

    sin.sin_port = htons(12345);

    sin.sin_addr.s_addr = INADDR_ANY;

    // 绑定 socket 到本地地址

    if (bind(sockfd, (const sockaddr *)&sin, sizeof(sin)) < 0)

    {

        perror("绑定失败!");

        close(sockfd);

        return 1;

    }

    // 开始监听客户端连接

    if (listen(sockfd, 1) < 0)

    {

        perror("监听失败!");

        close(sockfd);

        return 1;

    }

    std::cout << "等待客户端连接......" << std::endl;

    sockaddr_in cli;

    socklen_t cli_t = sizeof(cli);

    int sockfd_cli = accept(sockfd, (struct sockaddr *)&cli, &cli_t);

    if (sockfd_cli < 0)

    {

        perror("接受连接失败!");

        close(sockfd);

        return 1;

    }

    // 接收客户端数据

    char buf[1024];

    int read_ser = read(sockfd_cli, buf, sizeof(buf) - 1); // 从连接套接字读取数据

    if (read_ser < 0)

    {

        perror("读取数据失败!");

        close(sockfd_cli);

        close(sockfd);

        return 1;

    }

    buf[read_ser] = '\0'; // 添加字符串结束符

    std::cout << "读取的数据:" << buf << std::endl;

    // 回复客户端

    char buf_write[] = "你好";

    int write_ser = write(sockfd_cli, buf_write, sizeof(buf_write));

    if (write_ser < 0)

    {

        perror("发送数据失败!");

        close(sockfd_cli);

        close(sockfd);

        return 1;

    }

    // 关闭连接

    close(sockfd_cli);

    close(sockfd);

    return 0;

}

客户端

#include <sys/types.h>

#include <sys/socket.h>

#include <iostream>

#include <stdio.h>

#include <netinet/in.h>

#include <unistd.h>

#include <string.h>

#include <arpa/inet.h>

int main()

{

    // 创建 TCP 套接字

    int clifd = socket(AF_INET, SOCK_STREAM, 0);

    if (clifd < 0)

    {

        perror("创建套接字失败!");

        return 1;

    }

    // 设置服务器地址信息

    sockaddr_in clin;

    clin.sin_family = AF_INET;

    clin.sin_port = htons(12345);

    inet_pton(AF_INET, "127.0.0.1", &clin.sin_addr); // 设置本地环回地址

    // 连接服务器

    if (connect(clifd, (const sockaddr *)&clin, sizeof(clin)) < 0)

    {

        perror("建立连接失败!");

        close(clifd);

        return 1;

    }

    // 要发送的数据

    char buf[] = "不知道说什么";

    // 写数据

    int write_cli = write(clifd, buf, sizeof(buf));

    if (write_cli < 0)

    {

        perror("发送数据失败!");

        close(clifd);

        return 1;

    }

    // 接收服务器回复

    char buffer[1024] = {0};

    int n = read(clifd, buffer, sizeof(buffer) - 1);

    if (n > 0)

    {

        buffer[n] = '\0'; // 添加字符串结束符

        std::cout << "收到服务器回复:" << buffer << std::endl;

    }

    else if (n < 0)

    {

        perror("读取失败!");

    }

    close(clifd);

    return 0;

}

Makefile

all:client server

client:client.cpp

       g++ -o client client.cpp

server:server.cpp

       g++ -o server server.cpp

.PHONY:clean

clean:

       rm -f client server

13.3.3多进程示例

服务器

// server_fork.cpp

#include <sys/types.h>

#include <sys/socket.h>

#include <iostream>

#include <netinet/in.h>

#include <unistd.h>

#include <string.h>

#include <arpa/inet.h>

#include <signal.h>

int main()

{

    int listen_fd = socket(AF_INET, SOCK_STREAM, 0);

    if (listen_fd < 0)

    {

        perror("socket");

        return 1;

    }

    sockaddr_in server_addr{};

    server_addr.sin_family = AF_INET;

    server_addr.sin_port = htons(12345);

    server_addr.sin_addr.s_addr = INADDR_ANY;

    if (bind(listen_fd, (sockaddr *)&server_addr, sizeof(server_addr)) < 0)

    {

        perror("bind");

        return 1;

    }

    if (listen(listen_fd, 5) < 0)

    {

        perror("listen");

        return 1;

    }

    std::cout << "服务器启动,等待客户端连接..." << std::endl;

    signal(SIGCHLD, SIG_IGN); // 避免僵尸进程

    while (true)

    {

        sockaddr_in client_addr{};

        socklen_t client_len = sizeof(client_addr);

        int conn_fd = accept(listen_fd, (sockaddr *)&client_addr, &client_len);

        if (conn_fd < 0)

        {

            perror("accept");

            continue;

        }

        pid_t pid = fork();

        if (pid == 0)

        {

            // 子进程

            close(listen_fd); // 子进程不用监听 socket

            char buffer[1024] = {0};

            int n = read(conn_fd, buffer, sizeof(buffer) - 1);

            if (n > 0)

            {

                buffer[n] = '\0';

                std::cout << "收到客户端[" << inet_ntoa(client_addr.sin_addr) << "]: " << buffer << std::endl;

                const char *reply = "你好客户端,我是服务器!";

                write(conn_fd, reply, strlen(reply));

            }

            close(conn_fd);

            return 0;

        }

        else if (pid > 0)

        {

            // 父进程

            close(conn_fd); // 父进程不处理这个连接

        }

        else

        {

            perror("fork");

        }

    }

    close(listen_fd);

    return 0;

}

客户端

// client.cpp

#include <sys/types.h>

#include <sys/socket.h>

#include <iostream>

#include <netinet/in.h>

#include <unistd.h>

#include <string.h>

#include <arpa/inet.h>

#include <stdio.h>

int main()

{

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);

    if (sockfd < 0)

    {

        perror("socket");

        return 1;

    }

    sockaddr_in server_addr{};

    server_addr.sin_family = AF_INET;

    server_addr.sin_port = htons(12345);

    inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr);

    if (connect(sockfd, (sockaddr *)&server_addr, sizeof(server_addr)) < 0)

    {

        perror("connect");

        return 1;

    }

    const char *msg = "你好服务器!";

    write(sockfd, msg, strlen(msg));

    char buffer[1024] = {0};

    int n = read(sockfd, buffer, sizeof(buffer) - 1);

    if (n > 0)

    {

        buffer[n] = '\0';

        std::cout << "收到服务器回复:" << buffer << std::endl;

    }

    close(sockfd);

    return 0;

}

相关文章:

  • GIS-AI 融合引擎架构:智慧景区导览系统的毫秒级响应与千级并发优化实战
  • javaSE学习(前端基础知识)
  • GD32F303----内部Flash读写
  • Pytorch深度学习框架60天进阶学习计划 - 第39天:联邦学习系统
  • [MySQL] 表的内连和外连
  • 解决【远程主机可能不符合 glibc 和 libstdc++ Vs code 服务器的先决条件】
  • 基于Axure的动态面板旋转实现抽奖转盘
  • CAD导入arcgis中保持面积不变的方法
  • K8S-证书更新时-误删除组件-
  • OpenHarmony人才认证证书
  • SpringMVC基础三(json)
  • 学习MySQL的第七天
  • 【软考系统架构设计师】信息系统基础知识点
  • python基础14 gRPC的流式(stream)传输(二)
  • 网络互连与互联网2
  • 镜像端口及观察端口的配置
  • WebPages 对象
  • 复习防火墙(二)
  • 【KWDB 创作者计划】_二进制安装部署 KWDB 踩过的坑和经验
  • 苍穹外卖|第二篇
  • 英文版网站制作/免费自媒体网站
  • 深圳市门户网站建设企业/深圳竞价托管
  • 平台网站建设有哪些方面/googleplay官网
  • 关于网站建设的销售技巧/seo门户 site
  • 网站哪家做的比较好/免费职业技能培训网
  • 网站建设有哪些平台/友情链接交换标准