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_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();
}

在这里插入图片描述

进一步了解TCP

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

GeekPad智慧屏编程控制(二)

前面已经实现了智慧屏开关的控制了&#xff0c;接下来再继续实现消息的订阅。 先如下图所示增加几个控件&#xff0c;一个按钮&#xff0c;2个文本框&#xff0c;其中右下角的文本框显示的内容会比较多&#xff0c;需要打开多行和右侧滚动条。 然后添加订阅消息的事件&#xf…

Postgresql 开发环境搭建指南(WindowsLinux)

一、Postgresql 简介 PostgreSQL 是一个免费的对象-关系数据库服务器(ORDBMS)&#xff0c;在灵活的BSD许可证下发行。 RDBMS 是关系数据库管理系统&#xff0c;是建立实体之间的联系&#xff0c;最后得到的是关系表。 ORDBMS在原来关系数据库的基础上&#xff0c;增加了一些新…

设备智能化无线通信,ESP32-C2物联网方案,小尺寸芯片实现大功能

在科技飞速发展的当下&#xff0c;我们的生活正被各类智能设备悄然改变&#xff0c;它们如同一位位无声的助手&#xff0c;渗透到我们生活的每一个角落&#xff0c;让生活变得更加便捷和丰富多彩。 智能插座、智能照明和简单家电设备在家居领域的应用&#xff0c;为我们的生活…

Unity 编辑器热更C# FastScriptReload

工具源码&#xff1a;https://github.com/handzlikchris/FastScriptReload 介绍 用于运行时修改C#后能快速重新编译C#并生效&#xff0c;避免每次改C#&#xff0c;unity全部代码重新编译&#xff0c;耗时旧且需要重启游戏。 使用 需要手动调整AssetPipeline自动刷新模式&…

kbengine服务器和 数据库 系统路径配置

一、服务器 系统路径配置 二、mysql5.7.44 系统路径配置 mysql 压缩包安装方式 解压压缩包&#xff0c;将解压路径加入 系统环境。 或者 系统变量新增 变量名&#xff1a;MYSQL_HOME 变量值&#xff1a;C:\MyPrograms\mysql-8.0.12-winx64修改系统变量的 path 变量&#xff…

AI代码生成器如何重塑前端开发的工作环境

近年来&#xff0c;人工智能&#xff08;AI&#xff09;技术迅猛发展&#xff0c;深刻地改变着各行各业的工作方式。在软件开发领域&#xff0c;AI写代码工具的出现更是掀起了一场革命&#xff0c;尤其对前端开发工程师的工作环境和协作方式产生了深远的影响。本文将深入探讨AI…

前端可以不用依赖后端实现导出大数据了

theme: channing-cyan hightlight: channing-cyan 前言 在我们公司表格数据导出都是前端去处理。一开始数据量不大&#xff0c;倒没什么问题。但随着数据量的加大&#xff0c;问题也逐渐暴露出来。 一天的数据量有一来万条&#xff0c;导出一定时间范围的数据&#xff0c;30…

本地部署DeepSeek Nodejs版

目录 1.下载 Ollama 2.下载DeepSeek模型 3.下载 ollama.js 1.下载 Ollama https://ollama.com/ 下载之后点击安装&#xff0c;等待安装成功后&#xff0c;打开cmd窗口&#xff0c;输入以下指令&#xff1a; ollama -v 如果显示了版本号&#xff0c;则代表已经下载成功了。…

C++ Primer 迭代语句

欢迎阅读我的 【CPrimer】专栏 专栏简介&#xff1a;本专栏主要面向C初学者&#xff0c;解释C的一些基本概念和基础语言特性&#xff0c;涉及C标准库的用法&#xff0c;面向对象特性&#xff0c;泛型特性高级用法。通过使用标准库中定义的抽象设施&#xff0c;使你更加适应高级…

你需要提供管理员权限才能删除此文件夹解决方法

立即高级启动 windows10 搜索“设置”&#xff0c;然后“更新和安全””->“恢复”->“立即重新启动” windows11 搜索“设置”&#xff0c;然后“Windows更新”->“更新历史记录”->“恢复”->“立即重新启动” 疑难解答 点击“疑难解答” 高级选项 启…

408-数据结构

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

神经网络常见激活函数 9-CELU函数

文章目录 CELU函数导函数函数和导函数图像优缺点pytorch中的CELU函数tensorflow 中的CELU函数 CELU 连续可微指数线性单元&#xff1a;CELU&#xff08;Continuously Differentiable Exponential Linear Unit&#xff09;,是一种连续可导的激活函数&#xff0c;结合了 ELU 和 …

Ceph集群搭建2025(squid版)

squid版本维护年限 apt install -y cephadmecho >> "deb http://mirrors.163.com/ceph/debian-squid/ bookworm main" echo >> "deb-src http://mirrors.163.com/ceph/debian-squid/ bookworm main"#安装源 cephadm install #开始初始化一个最…

详解电子邮箱工作原理|SMTP、POP3、IMAP、SPF、MIME

写在前面 电子邮件&#xff08;Email&#xff09;是一种通过互联网进行异步通信的技术&#xff0c;工作原理涉及多个协议、服务器和客户端协同工作。 接下来我们来介绍一下电子邮箱的工作原理 1. 电子邮件的核心组成部分 邮件客户端&#xff1a;用户直接交互的软件&#xf…

【安全靶场】信息收集靶场

靶场&#xff1a;https://app.hackinghub.io/hubs/prison-hack 信息收集 子域名收集 1.subfinder files.jabprisons.com staging.jabprisons.com cobrowse.jabprisons.com a1.top.jabprisons.com cf1.jabprisons.com va.cobrowse.jabprisons.com vs.jabprisons.com c…

LVDS接口总结--(5)IDELAY3仿真

仿真参考资料如下&#xff1a; https://zhuanlan.zhihu.com/p/386057087 timescale 1 ns/1 ps module tb_idelay3_ctrl();parameter REF_CLK 2.5 ; // 400MHzparameter DIN_CLK 3.3 ; // 300MHzreg ref_clk ;reg …

DeepSeek的大模型介绍

文章目录 DeepSeek是什么DeepSeek平台使用DeepSeek的使用场景DeepSeek的本地部署 DeepSeek是什么 DeepSeek是一家2023/7月年成立的人工智能公司&#xff0c;致力于开发高效、高性能的生成式AI模型&#xff0c;在短短一年多的时间里推出了多款强大的开源模型&#xff0c;包括De…

【devops】Github Actions Secrets | 如何在Github中设置CI的Secret供CI的yaml使用

一、Github Actions 1、ci.yml name: CIon: [ push ]jobs:build:runs-on: ubuntu-lateststeps:- name: Checkout codeuses: actions/checkoutv3- name: Set up Gouses: actions/setup-gov4with:go-version: 1.23.0- name: Cache Go modulesuses: actions/cachev3with:path: |…

C语言基本概念————讨论sqrt()和pow()函数与整数的关系

本文来源&#xff1a;C语言基本概念——讨论sqrt()和pow()函数与整数的关系. C语言基本概念——sqrt和pow函数与整数的关系 1. 使用sqrt()是否可以得到完全平方数的精确的整数平方根1.1 完全平方数的计算结果是否精确&#xff1f;1.2 为什么不会出现误差&#xff08;如 1.99999…

日常知识点之面试后反思裸写string类

1&#xff1a;实现一个字符串类。 简单汇总 最简单的方案&#xff0c;使用一个字符串指针&#xff0c;以及实际字符串长度即可。 参考stl的实现&#xff0c;为了提升string的性能&#xff0c;实际上单纯的字符串指针和实际长度是不够了&#xff0c;如上&#xff0c;有优化方案…