Linux(socket网络编程)TCP连接
Linux(socket网络编程)TCP连接
- 基础
- 文件目录
- 函数
- 系统进程控制函数
- fork()
- exec系列函数
- void abort(void)
- void assert(int expression)
- void exit(int status)
- void _exit(int status)
- int atexit(void (*func)(void))
- int on_exit(void (*function)(int,void*),void *arg)
- int setjmp(jmp_buf environment)
- void longjmp(jmp_buf environment,int value)
- void siglongjmp(sigjmp_buf env,int val)
- int sigsetjmp(sigjmp_buf env,int savemask)
- pid_t getpgid(pid_t pid)
- pid_t getpgrp(void)
- pid_t getpid(void)
- pid_t getppid(void)
- int getpriority(int which,int who)
- int setpgid(pid_t pid,pid_t pgid)
- int setpgrp(void)
- int setpriority(int which,int who,int prio)
- int nice(int inc)
- system
- int wait(int *status)
- pid_t waitpid(pid_t pid,int* status,int options)
- Socket编程
- TCP和UDP
- 建立一个简单的TCP连接
- 服务端
- 创建socket
- 绑定socket到地址和端口
- 监听连接
- 接受连接
- 发送消息
- 关闭套接字
- 服务端完整代码
- 客户端
- 运行
- socket编程TCP连接:实验一 回声服务器
- socket编程TCP连接:实验二
基础
文件目录
Bin 命令文件
Boot 启动加载器
Dev 设备文件
Etc 配置文件
Home 普通用户家目录
Media 用于挂载可移动设备的目录
函数
字符串函数
数据转换函数
输入输出函数
权限控制函数
IO函数
系统进程控制函数
文件和目录函数
系统进程控制函数
进程是操作系统调度的最小单位
fork 用于创建一个新的子进程,子进程是父进程的副本。
exec 用于在当前进程的上下文中执行一个新的程序,替换当前进程的内存镜像。
fork()
》头文件#include <unistd.h>
》fork函数用于创建一个新的进程,也就是子进程,子进程是父进程的副本,父进程就是调用了fork的进程。
》子进程几乎拥有父进程的所有资源(包括内存、文件描述符等)
》子进程和父进程各自拥有独立的地址空间和进程ID
特点:
(1)返回两次:fork在父进程中返回子进程的PID,在子进程中返回0。如果创建失败,则返回-1。
(2)共享与独立:子进程和父进程共享打开的文件描述符、文件偏移量等。但它们有独立的地址空间和数据段。
(3)资源开销:fork会复制父进程的地址空间,这是一个相对昂贵的操作,尤其是在父进程占用大量内存时。不过,现代操作系统采用了写时复制机制(Copy-On-Write,COW)来优化这一过程。
exec系列函数
》头文件#include <unistd.h>
》exec系列函数用于在当前进程的上下文中执行一个新的程序,从而替换当前进程的镜像。
特点:
(1)不创建新的进程:exec不创建新的进程,而是用新的程序替换当前进程的内存空间
(2)参数传递:exec通常需要传递新程序的路径和参数列表
(3)无返回值:exec成功,无返回;失败返回-1。
常用函数:
execl(const char *path, const char *arg, ...)//使用路径和参数列表执行程序。
execle(const char *path, const char *arg, ..., char * const envp[])// 类似于 execl,但允许指定环境变量。
execlp(const char *file, const char *arg, ...)//使用文件名(在PATH中查找)和参数列表执行程序。
execv(const char *path, char *const argv[])// 使用路径和参数数组执行程序。
execve(const char *path, char *const argv[], char *const envp[])// 类似于 execv,但允许指定环境变量。
execvp(const char *file, char *const argv[])// 使用文件名(在PATH中查找)和参数数组执行程序。
l 进程执行的参数,以可变参数的形式给出的,这些参数以NULL作为最后一个参数结尾。
p 进程函数会将当前的PATH作为一个参考环境变量
e 进程函数会需要用户来设置这个环境变量
v 进程函数会用参数数组来传递argv,数组的最后一个必须是NULL
示例:
int main(int argc, char* argv[])
{execl("/bin/ls", "ls", "-l", NULL);
}
运行结果:
total 48
-rwxr-xr-x 1 root root 39008 Feb 10 16:46 ConsoleApplication5.out
void abort(void)
通常用于检测到不可恢复的错误时,比如内存分配失败
头文件#include<stdlib.h>
void assert(int expression)
用于在调试期间捕捉编程错误。它检查给定的表达式是否为真,如果为假,则输出错误信息并终止进程。
头文件#include<assert.h>
void exit(int status)
用于正常终止进程。它首先执行所有通过atexit()或on_exit()注册的函数,然后关闭所有打开的文件描述符,最后终止进程。
头文件#include<stdlib.h>
void _exit(int status)
终止进程,但不执行任何清理操作,也不刷新标准I/O缓存区。
头文件#include<unistd.h>
int atexit(void (*func)(void))
注册一个或多个函数。
头文件#include<stdlib.h>
int on_exit(void (function)(int,void),void *arg)
注册一个或多个函数,允许传递一个参数给注册的函数。
头文件#include<stdlib.h>
int setjmp(jmp_buf environment)
保存目前堆栈环境
void longjmp(jmp_buf environment,int value)
跳转到原先setjmp保存的堆栈环境
void siglongjmp(sigjmp_buf env,int val)
改变进程优先顺序,跳转到原先sigsetjmp保存的堆栈环境
int sigsetjmp(sigjmp_buf env,int savemask)
保存目前堆栈环境
pid_t getpgid(pid_t pid)
取得进程组识别码
pid_t getpgrp(void)
取得进程组识别码
pid_t getpid(void)
取得进程识别码
pid_t getppid(void)
取得父进程的进程识别码
int getpriority(int which,int who)
取得程序进程执行优先权
int setpgid(pid_t pid,pid_t pgid)
设置进程组识别码
int setpgrp(void)
设置进程组识别码
int setpriority(int which,int who,int prio)
设置程序进程执行优先权
int nice(int inc)
改变进程优先级
system
执行shell命令
int wait(int *status)
等待子进程中断或结束
pid_t waitpid(pid_t pid,int* status,int options)
等待子进程中断或结束
Socket编程
建立TCP连接
服务端:
Socket 封装底层逻辑,为应用程序提供便捷的通信接口
创建时需要指定:传输层协议和地址簇(IPv4/IPv6)
Bind 为socket绑定IP地址和端口号
Listen 设置为监听模式,设置最大连接数
Accept 接收连接,返回一个用于通信的新socket
Read/write 数据交换
Close 断开连接
客户端:
Socket
Connect
Read/write
Close
迭代服务器 一种服务器处理模式,特点是一次只处理一个请求,与之对应的是并发服务器。
就是把服务端上的accept,read/write,close等放到一个循环中,以便能多次接收客户端的请求。
回声服务器 把收到的数据原封不动的回复。用于测试
TCP套接字的 I/O缓冲 TCP协议在数据传输过程中,用来临时存放数据的内存区域,分发送缓冲区,和接收缓冲区。
TCP和UDP
TCP协议三次握手,四次挥手
UDP适用于实时音视频传输,因为更看重实时性,即便有丢包也只会造成短暂的画面抖动或杂音。
TCP能保证数据的完整性。适合用来传输重要的压缩文件。
TCP通常比UDP慢,有两个原因:
1.收发数据前后进行的连接设置及清理过程
2.收发数据过程中卫保证可靠性而添加的流控制
尤其是收发的数据量小但需要频繁连接时,UDP比TCP更高效
UDP中的服务器端和客户端没有连接。只有创建套接字的过程和数据交换过程
TCP中,服务端与每一个客户端通信都需要一个单独的套接字。而UDP中,无论与多少个客户端通信,服务端都只需要一个套接字。
对于UDP,调用sendto函数时自动分配IP和端口号。也就是说,UDP客户端中通常无需额外的地址分配过程。
TCP:服务端和客户端建立连接
服务端:
建立socket
bind给socket绑定IP和端口号
Listen开始监听
accept接收连接,三次握手在这里,返回一个新的用于通信的socket
客户端:
建立socket
connect 主动连接
数据交换:
Read、write
建立一个简单的TCP连接
初学阶段,如果搞两台主机来建立通信,先不说通信上的各种问题,但是运行调试就很麻烦。
所以为了更易于学习,在一个程序的不同进程中来实现服务端和客户端。
服务端
创建socket
struct sockaddr_in seraddr,cliaddr;//创建地址结构体
socklen_t cliaddrlen = sizeof(cliaddr);//客户端地址长度,socklen_t通常是一个无符号整型
// 创建socket
int server,client;//创建套接字
server = socket(PF_INET, SOCK_STREAM, 0);
不出意外的话,这里就得到了套接字,而要是server<0说明创建套接字失败了。
if (server < 0) {std::cout << "create socket failed!" << std::endl;}
socket函数:int socket(int domain, int type, int protocol);
域为PF_INET表示IPv4
类型为SOCK_STREAM表示TCP
protocol通常为0
struct sockaddr_in 是一个用来描述Internet地址的结构体
linux系统中的定义(c语言):
struct sockaddr_in {sa_family_t sin_family; // 地址族,通常为 AF_INET(IPv4)uint16_t sin_port; // 端口号,网络字节序(大端模式)struct in_addr sin_addr; // IPv4 地址,网络字节序char sin_zero[8]; // 填充字节,必须全为0(用于与 sockaddr 兼容)
};
绑定socket到地址和端口
memset(&seraddr, 0, sizeof(seraddr)); // 初始化地址结构体
seraddr.sin_family = AF_INET; // IPv4地址
seraddr.sin_addr.s_addr = inet_addr("0.0.0.0"); // 监听所有可用接口
seraddr.sin_port = htons(8888); // 端口号
int ret = bind(server, (struct sockaddr*)&seraddr, sizeof(seraddr));
if (ret == -1) {std::cout << "bind failed!" << std::endl;close(server);return;
}
sockaddr是一个通用的套接字地址结构体,定义在头文件sys/socket.h中,它包含了一些必要的字段,但字段并不具体:
struct sockaddr {sa_family_t sa_family; // 地址族(例如 AF_INET, AF_INET6)char sa_data[14]; // 地址数据,具体含义依赖于地址族
};
sockaddr_in 是专门用于IPv4的套接字地址结构体。 定义在头文件netinet/in.h
struct sockaddr_in {sa_family_t sin_family; // 地址族,对于IPv4地址,通常是 AF_INETuint16_t sin_port; // 端口号(网络字节序)struct in_addr sin_addr; // IPv4地址char sin_zero[8]; // 填充字节,为了保持与 struct sockaddr 结构的大小一致
};
监听连接
ret = listen(server, 3); // 最多允许3个待处理连接
if (ret == -1) {std::cout << "listen failed!" << std::endl;close(server);return;
}
接受连接
client = accept(server, (struct sockaddr*)&cliaddr, &cliaddrlen);
if (client == -1) {std::cout << "accept failed!" << std::endl;close(server);return;
}
发送消息
//向客户端发送消息
const char message[] = "Hello World!"; //要发送的消息
ssize_t len = write(client, message, strlen(message));
if (len != (ssize_t)strlen(message)) {std::cout << "write failed!" << std::endl;close(server);return;
}
关闭套接字
close(client);
close(server);
服务端完整代码
//头文件
#include <iostream> // 包含标准输入输出流库
#include <cstring> // 包含memset等字符串处理函数
#include <unistd.h> // 包含close函数
#include <arpa/inet.h> // 包含inet_addr, htons等网络地址转换函数
#include <sys/types.h> // 包含数据类型定义
#include <sys/socket.h> // 包含socket编程相关函数和结构体
#include <netinet/in.h> // 包含sockaddr_in结构体定义
void lession_ser()
{// 创建用于服务器端的socketint server; // 服务器socket描述符int client; // 客户端socket描述符(由accept返回)struct sockaddr_in seraddr, cliaddr; // 服务器端和客户端的地址结构体socklen_t cliaddrlen = sizeof(cliaddr); // 客户端地址长度// 创建socketserver = socket(PF_INET, SOCK_STREAM, 0);if (server < 0) {std::cout << "create socket failed!" << std::endl;return; // 创建失败,退出函数}// 绑定socket到指定地址和端口memset(&seraddr, 0, sizeof(seraddr)); // 清零结构体seraddr.sin_family = AF_INET; // 设置地址族为IPv4seraddr.sin_addr.s_addr = inet_addr("0.0.0.0"); // 绑定到所有可用接口seraddr.sin_port = htons(9527); // 设置端口号为9527(网络字节序)int ret = bind(server, (struct sockaddr*)&seraddr, sizeof(seraddr));if (ret == -1) {std::cout << "bind failed!" << std::endl;close(server); // 绑定失败,关闭socketreturn;}// 开始监听连接请求ret = listen(server, 3); // 监听队列长度为3printf("%s(%d):%s\n", __FILE__, __LINE__, __FUNCTION__);if (ret == -1) {std::cout << "listen failed!" << std::endl;close(server); // 监听失败,关闭socketreturn;}// 接受一个客户端连接printf("%s(%d):%s\n", __FILE__, __LINE__, __FUNCTION__); // 打印当前文件名、行号和函数名(调试用)client = accept(server, (struct sockaddr*)&cliaddr, &cliaddrlen);if (client == -1) {std::cout << "accept failed!" << std::endl;close(server); // 接受失败,关闭服务器socketreturn;}// 向客户端发送数据printf("%s(%d):%s\n", __FILE__, __LINE__, __FUNCTION__); // 打印当前文件名、行号和函数名(调试用)const char message[] = "Hello World!"; // 要发送的消息ssize_t len = write(client, message, strlen(message)); // 发送消息if (len != (ssize_t)strlen(message)) {std::cout << "write failed!" << std::endl;close(server); // 发送失败,关闭服务器socket(这里应该也关闭client,但示例中未做)return;}// 关闭socketclose(client); // 关闭客户端socketclose(server); // 关闭服务器socket// 注释:在实际应用中,通常服务器不会立即关闭,而是会继续监听新的连接。// 此处关闭是为了示例简洁。
}
客户端
// 客户端运行函数
void run_client()
{// 创建一个套接字int client = socket(PF_INET, SOCK_STREAM, 0);struct sockaddr_in servaddr; // 服务器地址结构体memset(&servaddr, 0, sizeof(servaddr)); // 将结构体清零servaddr.sin_family = AF_INET; // 设置地址族为IPv4servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 设置服务器IP地址为127.0.0.1(本地回环地址)servaddr.sin_port = htons(8888); // 设置服务器端口号为9527(网络字节序)// 连接到服务器int ret = connect(client, (struct sockaddr*)&servaddr, sizeof(servaddr));if (ret == 0) { // 连接成功printf("%s(%d):%s\n", __FILE__, __LINE__, __FUNCTION__); // 打印当前文件名、行号和函数名char buffer[256] = ""; // 创建接收数据的缓冲区read(client, buffer, sizeof(buffer)); // 从服务器读取数据到缓冲区std::cout << buffer; // 输出接收到的数据}else { // 连接失败printf("%s(%d):%s %d\n", __FILE__, __LINE__, __FUNCTION__, ret); // 打印错误信息}close(client); // 关闭套接字std::cout << "client done!" << std::endl; // 打印客户端完成信息
}// 示例函数:演示父子进程间的通信
void lession()
{pid_t pid = fork(); // 创建子进程std::cout << pid << std::endl;if (pid == 0) { // 如果是子进程// 等待一秒以确保服务器进程先启动sleep(1);run_client(); // 运行客户端}else if (pid > 0) { // 如果是父进程printf("%s(%d):%s\n", __FILE__, __LINE__, __FUNCTION__); // 打印当前文件名、行号和函数名lession_ser(); // 运行服务器int status = 0; // 用于存储子进程退出状态的变量wait(&status); // 等待子进程结束}else { // fork失败std::cout << "fork failed!" << pid << std::endl; // 打印错误信息}
}
运行
int main(int argc, char* argv[])
{lession();
}
结果
3311
/root/projects/ConsoleApplication5/main.cpp(103):lession
/root/projects/ConsoleApplication5/main.cpp(39):lession_ser
/root/projects/ConsoleApplication5/main.cpp(46):lession_ser
0
/root/projects/ConsoleApplication5/main.cpp(98):lession
/root/projects/ConsoleApplication5/main.cpp(79):run_client
/root/projects/ConsoleApplication5/main.cpp(54):lession_ser
Hello World!client done!
注,我在调试过程中发现accept失败的情况,原因是我的客户端地址长度没有初始化:
socklen_t cliaddrlen;// =sizeof(cliaddr); // 客户端地址长度
socket编程TCP连接:实验一 回声服务器
//与上述实现相比,这里用了迭代服务器,建立了两次连接。每次连接进行5次通信。
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <iostream>
#include <chrono>//void error_handling(char* message);void lession_ser()
{//创建socketint server;int client;struct sockaddr_in addr, cliaddr;socklen_t cliaddrlen = sizeof(cliaddr); // 客户端地址长度server = socket(PF_INET, SOCK_STREAM, 0);if (server < 0) {std::cout << "create socket failed!" << std::endl;return;}//bindmemset(&addr, 0, sizeof(addr));addr.sin_family = AF_INET;addr.sin_addr.s_addr = inet_addr("0.0.0.0");addr.sin_port = htons(8888);int ret = bind(server, (struct sockaddr*)&addr, sizeof(addr));if (ret == -1) {std::cout << "bind failed!" << std::endl;close(server);return;}//listenret = listen(server, 3);printf("%s(%d):%s\n", __FILE__, __LINE__, __FUNCTION__);if (ret == -1) {std::cout << "listen failed!" << std::endl;close(server);return;}char buffer[1024]{};for (int i=0;i<2;i++) {//acceptprintf("准备第%d次连接\n", i);client = accept(server, (struct sockaddr*)&cliaddr, &cliaddrlen);if (client == -1) {std::cout << "accept failed!" << std::endl;close(server);return;}//返回客户端发送的信息ssize_t len = 0;while (len = read(client, buffer, sizeof(buffer))) {len = write(client, buffer, len);if (len < 0) {std::cout << "write failed!" << std::endl;goto keep1;}memset(buffer, 0, len);}if (len <= 0) {std::cout << "read failed!" << std::endl;goto keep1;}keep1://close//可以不执行,因为服务端关闭的时候,客户端会自动关闭printf("socket\"client\"关闭!");close(client);}close(server);}void run_client()
{int client = socket(PF_INET, SOCK_STREAM, 0);struct sockaddr_in servaddr;memset(&servaddr, 0, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");servaddr.sin_port = htons(8888);int ret = connect(client, (struct sockaddr*)&servaddr, sizeof(servaddr));int i{5};while (ret == 0 && i--) {printf("%s(%d):%s\n", __FILE__, __LINE__, __FUNCTION__);auto now = std::chrono::system_clock::now();std::time_t now_time_t = std::chrono::system_clock::to_time_t(now);char* buffer = std::ctime(&now_time_t);write(client, buffer, sizeof(buffer));memset(buffer, 0, sizeof(buffer));read(client, buffer, sizeof(buffer));std::cout << buffer;}printf("red=%d\n", ret);close(client);std::cout << "client done!" << std::endl;
}#include <sys/wait.h>
#include "main.h"
void lession()
{pid_t pid = fork();std::cout << pid << std::endl;if (pid == 0) {//开启客户端printf("%s(%d):%s\n", __FILE__, __LINE__, __FUNCTION__);sleep(1);run_client();run_client();}else if (pid > 0) {printf("%s(%d):%s\n", __FILE__, __LINE__, __FUNCTION__);lession_ser();int status = 0;std::cout << "子进程\"" << wait(&status) << "\"结束!" << std::endl;}else {std::cout << "fork failed!" << pid << std::endl;}
}int main(int argc, char* argv[])
{lession();
}
运行结果

上述代码存在的问题:
(1)char* buffer,对buffer求长度时,用sizeof(buffer)得到的是指针类型的长度4/8,用sizeof(buffer)得到的是1。正确求法是用strlen(buffer);
用char buffer是为了接收获取到的时间信息,但继续用buffer作为接收缓冲区,其缓冲区就很小了(我这里只有25)。
所以:read和write时,要注意缓冲区的大小、count参数等信息。避免数据丢失。
socket编程TCP连接:实验二
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>//sockaddr_in、htons()
#include <string.h>
#include <arpa/inet.h>//inet_addr()、netinet/in.h
#include <unistd.h> // close()int compute(int count, int oprand[], char op) {int result = 0;switch (op) {case'+':for (int i = 0; i < count; i++)result += oprand[i];break;case'-':for (int i = 0; i < count; i++)result -= oprand[i];break;case'*':result = 1;for (int i = 0; i < count; i++)result *= oprand[i];break;default:break;}return result;
}void tcp_server() {//创建socketint server = socket(PF_INET, SOCK_STREAM, 0);if(server < 0)return;struct sockaddr_in addr;//绑定IP、端口memset(&addr, 0, sizeof(addr));addr.sin_family = AF_INET;addr.sin_addr.s_addr = inet_addr("0.0.0.0");addr.sin_port = htons(8888);int ret = bind(server, (struct sockaddr*)&addr, sizeof(addr));if (ret == -1) {close(server);return;}//listenret = listen(server, 3);if (ret == -1) {close(server);return;}//acceptstruct sockaddr_in cliaddr;socklen_t cliaddrlen = sizeof(cliaddr);char buffer[1024]{};while (1) {memset(buffer, 0, sizeof(buffer));int client = accept(server, (struct sockaddr*)&cliaddr, &cliaddrlen);if (client == -1) {close(server);return;}//readsize_t len = 0;len = read(client, buffer, 1);int result = 0;if (len > 0) {//加&0xFF的原因:当buffer[0]大于128时,其最高位为1,强制转换过程的右移会加1,对其结果进行&0xFF可以将高位多出的1变为0;for (int i = 0; i < ((unsigned)buffer[0] & 0xFF); i++)read(client, buffer + 1 + i * 4, 4);read(client, buffer + 1 + ((unsigned)buffer[0] & 0xFF)*4,1);result = compute(((unsigned)buffer[0]&0xFF), (int*)(buffer + 1), buffer[((unsigned)buffer[0] & 0xFF) * 4 + 1]);write(client, &result, 4);}close(client);}close(server);
}void tcp_client() {//创建socketint client = socket(PF_INET, SOCK_STREAM, 0);//connectstruct sockaddr_in addr;memset(&addr, 0, sizeof(addr));addr.sin_family = AF_INET;addr.sin_addr.s_addr = inet_addr("127.0.0.1");addr.sin_port = htons(8888);int ret = connect(client, (struct sockaddr*) & addr, sizeof(addr));char buffer[1024];while (ret == 0) {//memset(buffer,0,sizeof(buffer));fputs("Operand count:", stdout);int opnd_cnt = 0;scanf("%d", &opnd_cnt);if (opnd_cnt < 2 && opnd_cnt>255) {fputs("Error:opnd_cnt too small or opnd_cnt too big!\n", stdout);close(client);printf("client done!");return;}buffer[0] = (char)opnd_cnt;//服务器需要将buffer[0]解释为无符号类型for (int i = 0; i < opnd_cnt; i++)scanf("%d", buffer + 1 + i * 4);fgetc(stdin);fputs("Operator:", stdout);buffer[1 + opnd_cnt * 4] = fgetc(stdin);size_t len = opnd_cnt * 4 + 2;//strlen(buffer);size_t send_len = 0;printf("len = %d\n",len);while (send_len < len) {ssize_t ret = write(client, buffer + send_len, len - send_len);if (ret <= 0) {fputs("write failed!\n", stdout);close(client);printf("client done!\n");return;}send_len += (size_t)ret;}memset(buffer, 0, strlen(buffer));//准备接收服务器运算的结果if (read(client, buffer, sizeof(buffer)) <= 0) {printf("read failed!\n");close(client);return;}printf("from server:%d\n", *(int*)buffer);}close(client);printf("client done!\n");
}#include <sys/wait.h>
void test() {pid_t pid = fork();printf("---pid = %d---\n", pid);if (pid == 0) {//开启客户端sleep(1);tcp_client();tcp_client();}else if (pid > 0) {tcp_server();int status{};printf("子进程\"%d\"结束!", wait(&status));}else {printf("fork failed!");}
};int main() {test();
}

有问题:频繁出现read failed!
找原因:
客户端接收数据部分,只read一次,并且没有收到就结束,这样是有问题的。
因为服务端需要计算结果后发送给客户端。一旦客户端在服务端结果发送出来之前read,必然收不到数据(read函数返回0)。
修改后代码:
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>//sockaddr_in、htons()
#include <string.h>
#include <arpa/inet.h>//inet_addr()、netinet/in.h
#include <unistd.h> // close()int compute(int count, int oprand[], char op) {int result = 0;switch (op) {case'+':for (int i = 0; i < count; i++)result += oprand[i];break;case'-':for (int i = 0; i < count; i++)result -= oprand[i];break;case'*':result = 1;for (int i = 0; i < count; i++)result *= oprand[i];break;default:break;}return result;
}void tcp_server() {//创建socketint server = socket(PF_INET, SOCK_STREAM, 0);if(server < 0)return;struct sockaddr_in addr;//绑定IP、端口memset(&addr, 0, sizeof(addr));addr.sin_family = AF_INET;addr.sin_addr.s_addr = inet_addr("0.0.0.0");addr.sin_port = htons(8888);int ret = bind(server, (struct sockaddr*)&addr, sizeof(addr));if (ret == -1) {close(server);return;}//listenret = listen(server, 3);if (ret == -1) {close(server);return;}//acceptstruct sockaddr_in cliaddr;socklen_t cliaddrlen = sizeof(cliaddr);char buffer[1024]{};while (1) {memset(buffer, 0, sizeof(buffer));int client = accept(server, (struct sockaddr*)&cliaddr, &cliaddrlen);if (client == -1) {close(server);return;}//readsize_t len = 0;len = read(client, buffer, 1);int result = 0;if (len > 0) {//加&0xFF的原因:当buffer[0]大于128时,其最高位为1,强制转换过程的右移会加1,对其结果进行&0xFF可以将高位多出的1变为0;for (int i = 0; i < ((unsigned)buffer[0] & 0xFF); i++)read(client, buffer + 1 + i * 4, 4);read(client, buffer + 1 + ((unsigned)buffer[0] & 0xFF)*4,1);result = compute(((unsigned)buffer[0]&0xFF), (int*)(buffer + 1), buffer[((unsigned)buffer[0] & 0xFF) * 4 + 1]);write(client, &result, 4);}close(client);}close(server);
}void tcp_client() {//创建socketint client = socket(PF_INET, SOCK_STREAM, 0);//connectstruct sockaddr_in addr;memset(&addr, 0, sizeof(addr));addr.sin_family = AF_INET;addr.sin_addr.s_addr = inet_addr("127.0.0.1");addr.sin_port = htons(8888);int ret = connect(client, (struct sockaddr*) & addr, sizeof(addr));char buffer[1024];while (ret == 0) {//memset(buffer,0,sizeof(buffer));fputs("Operand count:", stdout);int opnd_cnt = 0;scanf("%d", &opnd_cnt);if (opnd_cnt < 2 && opnd_cnt>255) {fputs("Error:opnd_cnt too small or opnd_cnt too big!\n", stdout);close(client);printf("client done!");return;}buffer[0] = (char)opnd_cnt;//服务器需要将buffer[0]解释为无符号类型for (int i = 0; i < opnd_cnt; i++)scanf("%d", buffer + 1 + i * 4);fgetc(stdin);fputs("Operator:", stdout);buffer[1 + opnd_cnt * 4] = fgetc(stdin);size_t len = opnd_cnt * 4 + 2;//strlen(buffer);printf("len = %d\n", len);size_t send_len = 0;while (send_len < len) {ssize_t ret = write(client, buffer + send_len, len - send_len);if (ret <= 0) {fputs("write failed!\n", stdout);close(client);printf("client done!\n");return;}send_len += (size_t)ret;}memset(buffer, 0, strlen(buffer));//准备接收服务器运算的结果size_t read_len = 0;len = 4;while (read_len < 4){size_t ret = read(client, buffer + read_len, len - read_len);if (ret <= 0) {fputs("read failed!\n", stdout);close(client);std::cout << "client done!" << std::endl;return;}read_len += (size_t)ret;}printf("from server:%d\n", *(int*)buffer);}close(client);printf("client done!\n");
}#include <sys/wait.h>
void test() {pid_t pid = fork();printf("---pid = %d---\n", pid);if (pid == 0) {//开启客户端sleep(1);tcp_client();tcp_client();}else if (pid > 0) {tcp_server();int status{};printf("子进程\"%d\"结束!", wait(&status));}else {printf("fork failed!");}
};int main() {test();
}

还是有问题:每次运行,第二次计算都会出现read failed!
找原因:
客户端用了while(red==0),并且如果发送接收正常,while循环没有终止。而服务端一旦操作完成(计算并发送),就会close通信的socket。
此时,客户端write能够正常发出,但read就会返回-1。
修改后代码:
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>//sockaddr_in、htons()
#include <string.h>
#include <arpa/inet.h>//inet_addr()、netinet/in.h
#include <unistd.h> // close()int compute(int count, int oprand[], char op) {int result = 0;switch (op) {case'+':for (int i = 0; i < count; i++)result += oprand[i];break;case'-':for (int i = 0; i < count; i++)result -= oprand[i];break;case'*':result = 1;for (int i = 0; i < count; i++)result *= oprand[i];break;default:break;}std::cout << __LINE__ << ":result=" << result << std::endl;return result;
}void tcp_server() {//创建socketint server = socket(PF_INET, SOCK_STREAM, 0);if(server < 0)return;struct sockaddr_in addr;//绑定IP、端口memset(&addr, 0, sizeof(addr));addr.sin_family = AF_INET;addr.sin_addr.s_addr = inet_addr("0.0.0.0");addr.sin_port = htons(8888);int ret = bind(server, (struct sockaddr*)&addr, sizeof(addr));if (ret == -1) {close(server);return;}//listenret = listen(server, 3);if (ret == -1) {close(server);return;}//acceptstruct sockaddr_in cliaddr;socklen_t cliaddrlen = sizeof(cliaddr);char buffer[1024]{};while (1) {memset(buffer, 0, sizeof(buffer));int client = accept(server, (struct sockaddr*)&cliaddr, &cliaddrlen);if (client == -1) {close(server);std::cout << "accept failed!---server done!!!" << std::endl;return;}//readsize_t len = 0;len = read(client, buffer, 1);std::cout << __LINE__ <<":buffer[0] = "<<buffer[0]<< std::endl;int result = 0;if (len > 0) {//加&0xFF的原因:当buffer[0]大于128时,其最高位为1,强制转换过程的右移会加1,对其结果进行&0xFF可以将高位多出的1变为0;for (int i = 0; i < ((unsigned)buffer[0] & 0xFF); i++)read(client, buffer + 1 + i * 4, 4);read(client, buffer + 1 + ((unsigned)buffer[0] & 0xFF)*4,1);std::cout << __LINE__ << std::endl;result = compute(((unsigned)buffer[0]&0xFF), (int*)(buffer + 1), buffer[((unsigned)buffer[0] & 0xFF) * 4 + 1]);write(client, &result, 4);}std::cout << __LINE__ << "服务端已计算完成并发送!!!\n准备结束当前通信的socket,进入下一次循环,重新建立新的连接。" << std::endl;close(client);}close(server);
}void tcp_client() {//创建socketint client = socket(PF_INET, SOCK_STREAM, 0);//connectstruct sockaddr_in addr;memset(&addr, 0, sizeof(addr));addr.sin_family = AF_INET;addr.sin_addr.s_addr = inet_addr("127.0.0.1");addr.sin_port = htons(8888);int ret = connect(client, (struct sockaddr*) & addr, sizeof(addr));char buffer[1024];if (ret == 0) {//memset(buffer,0,sizeof(buffer));fputs("Operand count:", stdout);int opnd_cnt = 0;scanf("%d", &opnd_cnt);if (opnd_cnt < 2 && opnd_cnt>255) {fputs("Error:opnd_cnt too small or opnd_cnt too big!\n", stdout);close(client);printf("client done!");return;}buffer[0] = (char)opnd_cnt;//服务器需要将buffer[0]解释为无符号类型for (int i = 0; i < opnd_cnt; i++)scanf("%d", buffer + 1 + i * 4);fgetc(stdin);fputs("Operator:", stdout);buffer[1 + opnd_cnt * 4] = fgetc(stdin);size_t len = opnd_cnt * 4 + 2;//strlen(buffer);printf("len = %d\n", len);size_t send_len = 0;while (send_len < len) {ssize_t ret = write(client, buffer + send_len, len - send_len);if (ret <= 0) {fputs("write failed!\n", stdout);close(client);printf("client done!\n");return;}send_len += (size_t)ret;}std::cout << "Client sent successfully!!!" << std::endl;memset(buffer, 0, strlen(buffer));//准备接收服务器运算的结果size_t read_len = 0;len = 4;while (read_len < 4){size_t ret = read(client, buffer + read_len, len - read_len);if (ret <= 0) {fputs("read failed!\n", stdout);close(client);std::cout << "client done!" << std::endl;return;}read_len += (size_t)ret;}printf("from server:%d\n", *(int*)buffer);}close(client);printf("client done!\n");
}#include <sys/wait.h>
void test() {pid_t pid = fork();printf("---pid = %d---\n", pid);if (pid == 0) {//开启客户端sleep(1);tcp_client();tcp_client();}else if (pid > 0) {tcp_server();int status{};printf("子进程\"%d\"结束!", wait(&status));}else {printf("fork failed!");}
};int main() {test();
}

相关文章:
Linux(socket网络编程)TCP连接
Linux(socket网络编程)TCP连接 基础文件目录函数系统进程控制函数fork()exec系列函数void abort(void)void assert(int expression)void exit(int status)void _exit(int status)int atexit(void (*func)(void))int on_exit(void (*function)(int,void*)…...
力扣刷题 遍历字符串
根据网上的学习 对遍历字符串 学习了一些自己的见解。给一个字符串组成的句子(带空格或标点),然后对句中单个字符串进行一系列处理的题目 参考链接 1805. 字符串中不同整数的数目 - 力扣(LeetCode) 模版1 s " "; //这里在最后一…...
深度剖析观察者模式:从理论到实战的Java实现
在软件设计中,观察者模式(Observer Pattern) 是一种高频使用的行为型设计模式,它定义了对象之间一对多的依赖关系,使得当一个对象状态改变时,其所有依赖对象(观察者)会自动收到通知并…...
Rust学习总结之所有权(一)
不管是计算机的哪种语言,都有内存的管理方式。主流有两种,一是以C为代表的由开发者来决定申请和释放内存,二是以Python为代表的通过语言本身的垃圾回收机制来自动管理内存。Rust开辟了第三种方式,通过所有权系统管理内存。 Rust所…...
汇编简介常用语法
为什么要有汇编 因为Cortex-A芯片一上电SP指针还没初始化,C环境还没准备 好,所以肯定不能运行C代码,必须先用汇编语言设置好C环境,比如初始化DDR、设置SP 指针等等,当汇编把C环境设置好了以后才可以运行C代码 GNU语法…...
xtquant库在量化交易中的安装与实战应用
xtquant库在量化交易中的安装与实战应用 技术背景与应用场景 在量化交易领域,xtquant库作为迅投官方开发的Python包,扮演着至关重要的角色。它主要用于与MiniQMT通信,使得开发者能够获取MiniQMT中的数据,并下达交易指令。通过xt…...
ANR学习
一、ANR 概述 ANR 是 Android 系统用于监控应用是否及时响应的关键机制。形象地说,如同设置定时炸弹场景:系统的中控系统(system_server 进程)启动倒计时,若应用进程在规定时间内未完成特定任务,中控系统将…...
前端知识速记--JS篇:instanceof
前端知识速记–JS篇:instanceof 在JavaScript中,instanceof运算符用于检测一个对象是否是另一个对象的实例。它的基本语法为:obj instanceof Constructor。如果obj是Constructor的实例,它将返回true,否则返回false。这…...
Tcp_socket
Tcp不保证报文完整性(面向字节流) 所以我们需要在应用层指定协议,确保报文完整性 // {json} -> len\r\n{json}\r\n bool Encode(std::string &message) {if(message.size() 0) return false;std::string package std::to_string(m…...
idea插件开发,如何获取idea设置的系统语言
手打不易,如果转摘,请注明出处! 注明原文:https://zhangxiaofan.blog.csdn.net/article/details/145578160 版本要求 大于 2024.3 错误用法 网上有的说使用:UIUtil com.intellij.util.ui.UIUtil 代码示例…...
< 自用文儿 > 在 Ubuntu 24 卸载 Docker 应用软件与运行的容器
环境: Host: usw OS: Ubuntu 24.04 TLS 目标: 卸载在运行的 Docker APP。 (上运行了一个 container: 可以在线看 WSJ RSS 新闻,都 docker 预装两个网口,今天发现路由表有些看不懂,决定卸载) 卸载 Dock…...
基于 SpringBoot 和 Vue 的智能腰带健康监测数据可视化平台开发(文末联系,整套资料提供)
基于 SpringBoot 和 Vue 的智能腰带健康监测数据可视化平台开发 一、系统介绍 随着人们生活水平的提高和健康意识的增强,智能健康监测设备越来越受到关注。智能腰带作为一种新型的健康监测设备,能够实时采集用户的腰部健康数据,如姿势、运动…...
MySQL InnoDB引擎 MVCC
MVCC(Multi-Version Concurrency Control)即多版本并发控制,是 MySQL 的 InnoDB 存储引擎实现并发控制的一种重要技术。它在很多情况下避免了加锁操作,从而提高了数据库的并发性能。 一、原理 MVCC 的核心思想是通过保存数据在某…...
深入解析 STM32 GPIO:结构、配置与应用实践
理解 GPIO 的工作原理和配置方法是掌握 STM32 开发的基础,后续的外设(如定时器、ADC、通信接口)都依赖于 GPIO 的正确配置。 目录 一、GPIO 的基本概念 二、GPIO 的主要功能 三、GPIO 的内部结构 四、GPIO 的工作模式 1. 输入模式 2. 输…...
【Elasticsearch】管道聚合
管道聚合就是在已有聚合结果之上在进行聚合,管道聚合是针对于聚合的聚合 在 Elasticsearch 中,管道聚合(Pipeline Aggregations)是一种特殊的聚合类型,用于对其他聚合的结果进行进一步的计算和处理,而不是直…...
Python的那些事第十八篇:框架与算法应用研究,人工智能与机器学习
人工智能与机器学习:框架与算法应用研究 摘要 本文深入探讨了人工智能与机器学习领域的核心框架和技术,包括TensorFlow、PyTorch和Scikit-learn库。文章首先介绍了TensorFlow和PyTorch的安装与配置方法,详细阐述了它们的基础概念,…...
【大数据安全分析】为什么要用大数据技术进行安全分析?
在当今数字化浪潮的推动下,安全运营领域犹如一片广袤且复杂的战场。由于其涵盖范围极为宽泛,为了能更深入、精准地探讨相关内容,将目光聚焦于大数据安全分析方向显得尤为必要。一方面,大数据安全分析在安全运营领域占据着举足轻重的地位;另一方面,倘若自身对该领域较为熟…...
java微服务常用技术
Spring Cloud Alibaba 1 系统架构演进 随着互联网行业的发展,对服务的要求也越来越高,服务架构也从单体架构逐渐演变为现在流行的微服务架构。 1.1 单体架构 早期的软件系统通常是基于单体应用架构设计的,也就是将整个系统作为一个单一的、可执行的应用程序来构建和维护…...
【Qt 常用控件】多元素控件(QListWidget、QTabelWidgt、QTreeWidget)
**View和**Widget的区别? **View的实现更底层,**Widget是基于**View封装实现的更易用的类型。 **View使用MVC结构 MVC是软件开发中 经典的 软件结构 组织形式,软件设计模式。 M(model)模型。管理应用程序的核心数据和…...
ubuntu文件同步
1. 使用 rsync 同步文件 rsync 是一个常用的文件同步工具,可以在本地或远程系统之间同步文件和目录。 基本用法: rsync -avz /源目录/ 目标目录/-a:归档模式,保留文件属性。-v:显示详细输出。-z:压缩传输…...
解决VsCode的 Vetur 插件has no default export Vetur问题
文章目录 前言1.问题2. 原因3. 解决其他 前言 提示: 1.问题 Cannot find module ‘ant-design-vue’. Did you mean to set the ‘moduleResolution’ option to ‘node’, or to add aliases to the ‘paths’ option? Module ‘“/xxx/xxx/xxx/xxx/xxx/src/vie…...
DeepSeek本地部署详细指南
DeepSeek本地部署详细指南 随着人工智能技术的飞速发展,本地部署大模型的需求也日益增加。DeepSeek作为一款开源且性能强大的大语言模型,提供了灵活的本地部署方案,让用户能够在本地环境中高效运行模型,同时保护数据隐私。以下是…...
DNS污染:网络世界的“隐形劫持”与防御
在互联网的底层架构中,DNS(域名系统)如同数字世界的“导航员”,将用户输入的域名翻译成机器可读的IP地址。然而,DNS污染(DNS Poisoning)正像一场无声的“地址篡改”危机,威胁着全球网…...
AF3 superimpose函数解读
AlphaFold3 superimpose函数通过使用SVD最小化RMSD,将坐标叠加到参考上,在蛋白质结构预测中用于比较预测结构与真实结构的相似性。 源代码: from src.utils.geometry.alignment import weighted_rigid_align from src.utils.geometry.vect…...
python制作自己的一款Markdowm格式消除工具
01 引言 在日常使用 Markdown 编写文档时,我们有时会需要将 Markdown 格式的文本转换为纯文本,去除其中的各种标记符号,如标题符号、列表符号、代码块标记等。手动去除这些标记不仅效率低下,还容易出错。本文将介绍如何使用 Pyt…...
【C#零基础从入门到精通】(三)——C#变量和数据类型详解
【C#零基础从入门到精通】(三)——C#变量和数据类型详解 数据类型 在 C# 中,数据类型是对数据进行分类的方式,它定义了变量可以存储的数据的种类、范围以及可以对这些数据执行的操作。C# 的数据类型主要分为值类型、引用类型和指针类型(指针类型通常在不安全代码中使用),…...
如何从头训练大语言模型: A simple technical report
今天来快速捋一下路线,写个简短的technical report,更多是原理介绍性的。按我个人理解,从最简单的部分开始,逐步过渡到最繁复的环节: 模型架构-> Pretrain -> Post-Train -> Infra -> 数据侧。再掺杂一些杂项…...
gitlab无法登录问题
在我第一次安装gitlab的时候发现登录页面是 正常的页面应该是 这种情况的主要原因是不是第一次登录,所以我们要找到原先的密码 解决方式: [rootgitlab ~]# vim /etc/gitlab/initial_root_password# WARNING: This value is valid only in the followin…...
食品饮料生产瓶颈?富唯智能协作机器人来 “破壁”
在食品和饮料行业的发展进程中,诸多生产瓶颈如重复性劳动负担、复杂环境作业难题、季节性产能波动等,长期制约着企业的高效运营与进一步发展。如今,富唯智能协作机器人的出现,为这些难题提供了完美的解决方案,正逐步改…...
Python 实现 macOS 系统代理的设置
设置 SOCKS 代理 在 macOS 系统中,可以通过 networksetup 工具来设置 SOCKS 代理。以下是 Python 实现的方法: 使用 networksetup 设置 SOCKS 代理 import subprocessdef set_socks_proxy(server, port):"""设置 macOS 系统的 SOCKS 代理…...
