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连接:实验二
      • 进一步了解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();
}

在这里插入图片描述

进一步了解TCP

Linux(socket网络编程)I/O缓冲、三次握手、四次挥手

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/16882.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

408-数据结构

数据结构在学什么&#xff1f; 1.用代码把问题信息化 2.用计算机处理信息 ch1 数据&#xff1a;数据是信息的载体&#xff0c;是描述客观事物属性的数、字符及所有能输入到计算机中并被计算机程序识别和处理的符号的集合。数据是计算机程序加工的原料。 ch2 //假设线性表…

Go语言开发桌面应用基础框架(wails v3)-开箱即用框架

前言 本文是介绍如何集成好了Wails3开发框架以及提供视频教程&#xff0c;当你需要桌面开发时&#xff0c;直接下载我们基础框架代码&#xff0c;开箱即用不用配置开发需要依赖。 为什么使用v3版本&#xff0c;主要是v3新增的功能 ​支持多个窗口&#xff1a;在单个应用程序…

Git 与 Git常用命令

Git 是一个开源的分布式版本控制系统&#xff0c;广泛用于源代码管理。与传统的集中式版本控制系统不同&#xff0c;Git 允许每个开发者在本地拥有完整的代码库副本&#xff0c;支持离线工作和高效的分支管理。每次提交时&#xff0c;Git 会对当前项目的所有文件创建一个快照&a…

尚硅谷爬虫note004

一、urllib库 1. python自带&#xff0c;无需安装 # _*_ coding : utf-8 _*_ # Time : 2025/2/11 09:39 # Author : 20250206-里奥 # File : demo14_urllib # Project : PythonProject10-14#导入urllib.request import urllib.request#使用urllib获取百度首页源码 #1.定义一…

老WinForm中一个执行文件使用SQLite数据库

EF6在老WinForm中停止更新了&#xff0c;但如果只是在win10上面使用&#xff0c;老的.net Framework 4.8框架有一个优势&#xff0c;编译后的执行文件很小。还有一些老类库也只能在老的.net Framework 4.8框架使用&#xff0c;所以微软还是保留了老的.net Framework 4.8框架。 …

diff算法简析

diff算法的核心目的是用最少的步骤找出新旧节点的差异&#xff0c;从而更新视图。 diff算法是一种通过同层的树节点进行比较的高效算法&#xff0c;探讨的是虚拟DOM树发生变化后&#xff0c;生成DOM树更新补丁的方式。对比新旧两株虚拟DOM树的差异&#xff0c;将更新补丁作用于…

19.3 连接数据库

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 ​​​​​​​需要北风数据库的请留言自己的信箱。 连接数据库使用OleDbConnection&#xff08;数据连接&#xff09;类&#xff…

Redis实现分布式锁

一、使用分布式锁的背景是什么 1、如果你公司的业务&#xff0c;各个应用都只部署了一台机器&#xff0c;那么完全用不着分布式锁&#xff0c;直接使用Java的锁即可 2、可是当你们的业务量大&#xff0c;多台机器并发情况下争夺一个资源的时候&#xff0c;就必须要保证业务的…

变化检测相关论文可读list

一些用得上的&#xff1a; 遥感变化检测常见数据集https://github.com/rsdler/Remote-Sensing-Change-Detection-Dataset/ 代码解读&#xff1a;代码解读 | 极简代码遥感语义分割&#xff0c;结合GDAL从零实现&#xff0c;以U-Net和建筑物提取为例 对本list的说明&#xff1a;…

docker 逃逸突破边界

免责声明 本博客文章仅供教育和研究目的使用。本文中提到的所有信息和技术均基于公开来源和合法获取的知识。本文不鼓励或支持任何非法活动&#xff0c;包括但不限于未经授权访问计算机系统、网络或数据。 作者对于读者使用本文中的信息所导致的任何直接或间接后果不承担任何…

cv2.Sobel

1. Sobel 算子简介 Sobel 算子是一种 边缘检测算子&#xff0c;通过对图像做梯度计算&#xff0c;可以突出边缘。 Sobel X 方向卷积核&#xff1a; 用于计算 水平方向&#xff08;x 方向&#xff09; 的梯度。 2. 输入图像示例 假设我们有一个 55 的灰度图像&#xff0c;像素…

网络编程 day3

思维导图 以select函数模型为例 思维导图2 对应 epoll模型 应使用的函数 题目 使用epoll函数实现 两个客户端 通过服务器 实现聊天 思路 在原先代码基础上 实现 服务器 发向 客户端 使用客户端在服务器上的 套接字描述符 实现 客户端 接收 服务器…

Java 同步锁性能的最佳实践:从理论到实践的完整指南

目录 一、同步锁性能分析 &#xff08;一&#xff09;性能验证说明 1. 使用同步锁的代码示例 2. 不使用同步锁的代码示例 3. 结果与讨论 &#xff08;二&#xff09;案例初步优化分析说明 1. 使用AtomicInteger原子类尝试优化分析 2. 对AtomicInteger原子类进一步优化 …

Mac之JDK安装

Mac之JDK安装 一.安装 jdk 打开终端输入命令:java -version 查看是否已安装 JDK Oracle 官方下载地址 根据自己Mac 系统安装 查看 Mac 系统&#xff0c;打开中断命令&#xff0c;输入: uname -a Compressed Archive 是压缩文档&#xff0c;下载的是一个 .tar.gz 压缩包 D…

[MySQL]5-MySQL扩展(分片)

随着数据量和用户量增加&#xff0c;MySQL会有读写负载限制。以下是部分解决方案 目录 功能拆分 使用读池拓展读&#xff08;较复杂&#xff09; 排队机制 &#x1f31f;分片拓展写 按业务或职责划分节点或集群 大数据集切分 分片键的选择 多个分片键 跨分片查询 资料…

芯盾时代数据安全产品体系,筑牢数据安全防线

芯盾时代数据安全治理&#xff08;DSG&#xff09;框架&#xff0c;以国家法律法规、行业监管标准、行业最佳实践为依据&#xff0c;从数据安全战略出发&#xff0c;以数据分类分级为支撑&#xff0c;构数据安全管理体系、数据安全技术体系、数据安全运营体系与数据安全监督评价…

腾讯大数据基于 StarRocks 的向量检索探索

作者&#xff1a;赵裕隆&#xff0c;腾讯大数据研发工程师 本文整理自腾讯大数据工程师在 StarRocks 年度峰会上的分享&#xff0c;深入探讨了向量检索技术的原理与应用。此功能已应用到腾讯内部多个场景&#xff0c;引入 StarRocks 后&#xff0c;业务不仅不需要维护多套数据库…

STM32 RTC 实时时钟说明

目录 背景 RTC(实时时钟)和后备寄存器 32.768HZ 如何产生1S定时 RTC配置程序 第一次上电RTC配置 第1步、启用备用寄存器外设时钟和PWR外设时钟 第2步、使能RTC和备份寄存器访问 第3步、备份寄存器初始化 第4步、开启LSE 第5步、等待LSE启动后稳定状态 第6步、配置LSE为…

android studio在gradle的build时kaptDebugKotlin这个task需要执行很久

只修改了一点java代码&#xff0c;kaptDebugKotlin这个任务却执行了3~5分钟。。。

机器学习(李宏毅)——self-Attention

一、前言 本文章作为学习2023年《李宏毅机器学习课程》的笔记&#xff0c;感谢台湾大学李宏毅教授的课程&#xff0c;respect&#xff01;&#xff01;&#xff01; 二、大纲 何为self-Attention&#xff1f;原理剖析self-Attention VS CNN、RNN、GNN 三、何为self-Attenti…