Linux(socket网络编程)TCP连接

news2025/2/13 5:29:20

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_INET
    uint16_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()
{
    // 创建用于服务器端的socket
    int server; // 服务器socket描述符
    int client; // 客户端socket描述符(由accept返回)

    struct sockaddr_in seraddr, cliaddr; // 服务器端和客户端的地址结构体
    socklen_t cliaddrlen = sizeof(cliaddr); // 客户端地址长度

    // 创建socket
    server = 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; // 设置地址族为IPv4
    seraddr.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); // 绑定失败,关闭socket
        return;
    }

    // 开始监听连接请求
    ret = listen(server, 3); // 监听队列长度为3
    printf("%s(%d):%s\n", __FILE__, __LINE__, __FUNCTION__);
    if (ret == -1) {
        std::cout << "listen failed!" << std::endl;
        close(server); // 监听失败,关闭socket
        return;
    }

    // 接受一个客户端连接
    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); // 接受失败,关闭服务器socket
        return;
    }

    // 向客户端发送数据
    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;
    }

    // 关闭socket
    close(client); // 关闭客户端socket
    close(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; // 设置地址族为IPv4
	servaddr.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()
{
	//创建socket
	int 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;
	}
	//bind
	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) {
		std::cout << "bind failed!" << std::endl;
		close(server);
		return;
	}
	//listen
	ret = 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++) {
		//accept
		printf("准备第%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() {
	//创建socket
	int 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;
	}
	//listen
	ret = listen(server, 3);
	if (ret == -1) {
		close(server);
		return;
	}
	//accept
	struct 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;
		}

		//read
		size_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() {
	//创建socket
	int client = socket(PF_INET, SOCK_STREAM, 0);
	//connect
	struct 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() {
	//创建socket
	int 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;
	}
	//listen
	ret = listen(server, 3);
	if (ret == -1) {
		close(server);
		return;
	}
	//accept
	struct 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;
		}

		//read
		size_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() {
	//创建socket
	int client = socket(PF_INET, SOCK_STREAM, 0);
	//connect
	struct 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() {
	//创建socket
	int 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;
	}
	//listen
	ret = listen(server, 3);
	if (ret == -1) {
		close(server);
		return;
	}
	//accept
	struct 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;
		}

		//read
		size_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() {
	//创建socket
	int client = socket(PF_INET, SOCK_STREAM, 0);
	//connect
	struct 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();
}

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Rust学习总结之所有权(一)

不管是计算机的哪种语言&#xff0c;都有内存的管理方式。主流有两种&#xff0c;一是以C为代表的由开发者来决定申请和释放内存&#xff0c;二是以Python为代表的通过语言本身的垃圾回收机制来自动管理内存。Rust开辟了第三种方式&#xff0c;通过所有权系统管理内存。 Rust所…

汇编简介常用语法

为什么要有汇编 因为Cortex-A芯片一上电SP指针还没初始化&#xff0c;C环境还没准备 好&#xff0c;所以肯定不能运行C代码&#xff0c;必须先用汇编语言设置好C环境&#xff0c;比如初始化DDR、设置SP 指针等等&#xff0c;当汇编把C环境设置好了以后才可以运行C代码 GNU语法…

ANR学习

一、ANR 概述 ANR 是 Android 系统用于监控应用是否及时响应的关键机制。形象地说&#xff0c;如同设置定时炸弹场景&#xff1a;系统的中控系统&#xff08;system_server 进程&#xff09;启动倒计时&#xff0c;若应用进程在规定时间内未完成特定任务&#xff0c;中控系统将…

Tcp_socket

Tcp不保证报文完整性&#xff08;面向字节流&#xff09; 所以我们需要在应用层指定协议&#xff0c;确保报文完整性 // {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…

< 自用文儿 > 在 Ubuntu 24 卸载 Docker 应用软件与运行的容器

环境&#xff1a; Host: usw OS: Ubuntu 24.04 TLS 目标: 卸载在运行的 Docker APP。 &#xff08;上运行了一个 container: 可以在线看 WSJ RSS 新闻&#xff0c;都 docker 预装两个网口&#xff0c;今天发现路由表有些看不懂&#xff0c;决定卸载&#xff09; 卸载 Dock…

基于 SpringBoot 和 Vue 的智能腰带健康监测数据可视化平台开发(文末联系,整套资料提供)

基于 SpringBoot 和 Vue 的智能腰带健康监测数据可视化平台开发 一、系统介绍 随着人们生活水平的提高和健康意识的增强&#xff0c;智能健康监测设备越来越受到关注。智能腰带作为一种新型的健康监测设备&#xff0c;能够实时采集用户的腰部健康数据&#xff0c;如姿势、运动…

Python的那些事第十八篇:框架与算法应用研究,人工智能与机器学习

人工智能与机器学习&#xff1a;框架与算法应用研究 摘要 本文深入探讨了人工智能与机器学习领域的核心框架和技术&#xff0c;包括TensorFlow、PyTorch和Scikit-learn库。文章首先介绍了TensorFlow和PyTorch的安装与配置方法&#xff0c;详细阐述了它们的基础概念&#xff0c…

java微服务常用技术

Spring Cloud Alibaba 1 系统架构演进 随着互联网行业的发展,对服务的要求也越来越高,服务架构也从单体架构逐渐演变为现在流行的微服务架构。 1.1 单体架构 早期的软件系统通常是基于单体应用架构设计的,也就是将整个系统作为一个单一的、可执行的应用程序来构建和维护…

【Qt 常用控件】多元素控件(QListWidget、QTabelWidgt、QTreeWidget)

**View和**Widget的区别&#xff1f; **View的实现更底层&#xff0c;**Widget是基于**View封装实现的更易用的类型。 **View使用MVC结构 MVC是软件开发中 经典的 软件结构 组织形式&#xff0c;软件设计模式。 M&#xff08;model&#xff09;模型。管理应用程序的核心数据和…

解决VsCode的 Vetur 插件has no default export Vetur问题

文章目录 前言1.问题2. 原因3. 解决其他 前言 提示&#xff1a; 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…

python制作自己的一款Markdowm格式消除工具

01 引言 在日常使用 Markdown 编写文档时&#xff0c;我们有时会需要将 Markdown 格式的文本转换为纯文本&#xff0c;去除其中的各种标记符号&#xff0c;如标题符号、列表符号、代码块标记等。手动去除这些标记不仅效率低下&#xff0c;还容易出错。本文将介绍如何使用 Pyt…

如何从头训练大语言模型: A simple technical report

今天来快速捋一下路线&#xff0c;写个简短的technical report&#xff0c;更多是原理介绍性的。按我个人理解&#xff0c;从最简单的部分开始&#xff0c;逐步过渡到最繁复的环节: 模型架构-> Pretrain -> Post-Train -> Infra -> 数据侧。再掺杂一些杂项&#xf…

gitlab无法登录问题

在我第一次安装gitlab的时候发现登录页面是 正常的页面应该是 这种情况的主要原因是不是第一次登录&#xff0c;所以我们要找到原先的密码 解决方式&#xff1a; [rootgitlab ~]# vim /etc/gitlab/initial_root_password# WARNING: This value is valid only in the followin…

食品饮料生产瓶颈?富唯智能协作机器人来 “破壁”

在食品和饮料行业的发展进程中&#xff0c;诸多生产瓶颈如重复性劳动负担、复杂环境作业难题、季节性产能波动等&#xff0c;长期制约着企业的高效运营与进一步发展。如今&#xff0c;富唯智能协作机器人的出现&#xff0c;为这些难题提供了完美的解决方案&#xff0c;正逐步改…

Python 实现 macOS 系统代理的设置

设置 SOCKS 代理 在 macOS 系统中&#xff0c;可以通过 networksetup 工具来设置 SOCKS 代理。以下是 Python 实现的方法&#xff1a; 使用 networksetup 设置 SOCKS 代理 import subprocessdef set_socks_proxy(server, port):"""设置 macOS 系统的 SOCKS 代理…

深度学习之神经网络框架搭建及模型优化

神经网络框架搭建及模型优化 目录 神经网络框架搭建及模型优化1 数据及配置1.1 配置1.2 数据1.3 函数导入1.4 数据函数1.5 数据打包 2 神经网络框架搭建2.1 框架确认2.2 函数搭建2.3 框架上传 3 模型优化3.1 函数理解3.2 训练模型和测试模型代码 4 最终代码测试4.1 SGD优化算法…

【设计模式】【行为型模式】命令模式(Command)

&#x1f44b;hi&#xff0c;我不是一名外包公司的员工&#xff0c;也不会偷吃茶水间的零食&#xff0c;我的梦想是能写高端CRUD &#x1f525; 2025本人正在沉淀中… 博客更新速度 &#x1f4eb; 欢迎V&#xff1a; flzjcsg2&#xff0c;我们共同讨论Java深渊的奥秘 &#x1f…

C++模拟实现AVL树

目录 1.文章概括 2.AVL树概念 3.AVL树的性质 4.AVL树的插入 5.旋转控制 1.左单旋 2. 右单旋 3.左右双旋 4.右左双旋 6.全部代码 1.文章概括 本文适合理解平衡二叉树的读者阅读&#xff0c;因为AVL树是平衡二叉树的一种优化&#xff0c;其大部分实现逻辑与平衡二叉树是…

python卷积神经网络人脸识别示例实现详解

目录 一、准备 1&#xff09;使用pytorch 2&#xff09;安装pytorch 3&#xff09;准备训练和测试资源 二、卷积神经网络的基本结构 三、代码实现 1&#xff09;导入库 2&#xff09;数据预处理 3&#xff09;加载数据 4&#xff09;构建一个卷积神经网络 5&#xff0…

以Unity6.0为例,如何在Unity中开启DLSS功能

DLSS DLSS&#xff08;NVIDIA 深度学习超级采样&#xff09;&#xff1a;NVIDIA DLSS 是一套由 GeForce RTX™ Tensor Core 提供支持的神经渲染技术&#xff0c;可提高帧率&#xff0c;同时提供可与原生分辨率相媲美的清晰、高质量图像。目前最新突破DLSS 4 带来了新的多帧…