TCP服务器/客户端的Linux下的代码实现,单向通信、双向通信

本文主要是介绍TCP服务器/客户端的Linux下的代码实现,单向通信、双向通信,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

tcp流程

服务器:socket() --> bind() --> listen() --> accept() --> read/write or recv/send–>close()
客户端:socket() --> connect() --> read/write or recv/send -->close()
在这里插入图片描述

测试例子:

1.客户端给服务器发送一个字符串,服务器返回给客户端这个字符串的长度 。
2.实现一个时间服务器,客户端发送time,服务器返回当前时间。
3.如果客户端发送get 1.txt的请求,服务器获取文件内容后,发送给客户端 。

服务器:

/*===============================================*   文件名称:server.c*   创 建 者:   xm  *   创建日期:2022年08月16日*   描    述:================================================*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>
#include <sys/stat.h>
#include <fcntl.h>
void fun_work1(char *str,int connfd);//例子1
void fun_work2(int connfd);//例子2
int fun_work3(int connfd);//例子3
int main(int argc, char *argv[])
{ int sockfd=socket(PF_INET,SOCK_STREAM,0);if(sockfd<0){perror("socket");return -1;}//端口复用函数,为解决端口号被系统占用的情况(如由强制关闭服务器引起)int on=1;int k=setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));if(k==-1){perror("setsockopt");return -1;}struct sockaddr_in server_addr={0};server_addr.sin_family=AF_INET;server_addr.sin_port=htons(9000);//server_addr.sin_addr.s_addr=inet_addr("0.0.0.0");//能匹配本机所有ip地址server_addr.sin_addr.s_addr=htonl(INADDR_ANY);//也可以用宏,相当于0.0.0.0int len_ser=(int)sizeof(server_addr);if(bind(sockfd,(struct sockaddr*)&server_addr,len_ser)==-1){perror("bind");return -1;}if(listen(sockfd,10)==-1){perror("listen");return -1;}struct sockaddr_in client_addr={0};int len_cli=sizeof(client_addr);while(1)//循环服务器,解决客户端强制关闭导致服务器死循环或者直接退出的情况{		printf("wait a client....\n");int connfd=accept(sockfd,(struct sockaddr*)&client_addr,&len_cli);if(connfd<0){perror("accept");return -1;}printf("client ip:%s  client port:%d\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));//收发数据char buf[64]={0};while(1){int m=read(connfd,buf,64);if(m<0){perror("read");return -1;}else if(m==0)//客户端关闭,客户端用ctrl+c强制退出也算关闭{close(connfd);break;}printf(">%s\n",buf);int ret;if(strcmp(buf,"time")==0)//例子2{fun_work2(connfd);}else if(strcmp(buf,"get 1.txt")==0)//例子3{ret=fun_work3(connfd);}else{fun_work1(buf,connfd);//例子1}memset(buf,0,64);}}close(sockfd);return 0;   
} 
void fun_work1(char *buf,int connfd)
{char *str=buf;sprintf(str,"%ld",strlen(str));write(connfd,str,strlen(str));
}
void fun_work2(int connfd)
{char buf[64]={0};time_t mytime;time(&mytime);char *str=ctime(&mytime);strcpy(buf,str);write(connfd,buf,strlen(buf));}
int fun_work3(int connfd)
{int cp_fd=open("./../1.txt",O_RDONLY);if(cp_fd<0){perror("open");return -1;}int m,ret;char buf[64]={0};while((m=read(cp_fd,buf,64))>0){ret=write(connfd,buf,m);if(ret<0){perror("write");return -1;}fputs(buf,stdout);}printf("获取1.txt文件成功\n");close(cp_fd);}

客户端:

/*===============================================
*   文件名称:client.c
*   创 建 者:   xm  
*   创建日期:2022年08月16日
*   描    述:
================================================*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <fcntl.h>
int client_work1(int sockfd);//例子1
int  client_work2(int sockfd);//例子2
int  client_work3(int sockfd);//例子3
int main(int argc, char *argv[])
{ int sockfd=socket(PF_INET,SOCK_STREAM,0);//创建套接字if(sockfd<0){perror("socket");return -1;}struct sockaddr_in client_addr={0};client_addr.sin_family=AF_INET;client_addr.sin_port=htons(9000);//要连接的服务器的端口号//client_addr.sin_addr.s_addr=inet_addr("0.0.0.0");//能匹配本机所有ip地址client_addr.sin_addr.s_addr=htonl(INADDR_ANY);//也可以用宏,相当于0.0.0.0int len_cli=(int)sizeof(client_addr);if(connect(sockfd,(struct sockaddr*)&client_addr,len_cli)==-1){perror("connect");return -1;}printf("client ip:%s  client port:%d\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));//收发数据char buf[64]={0};while(1){printf("\n--------特殊功能关键字--------\n");printf("----quit---- 关闭客户端\n");printf("----time---- 查看当前时间\n");printf("----get 1.txt---- 下载文件测试\n");printf("input:\n");		fgets(buf,64,stdin);buf[strlen(buf)-1]='\0';//去掉换行符int m=write(sockfd,buf,64);if(m<0){perror("write");return -1;}else if(strcmp(buf,"quit")==0)//客户端关闭{	break;}if(strcmp(buf,"time")==0){if(client_work2(sockfd)<0)//例子2{perror("work2");return -1;}}else if(strcmp(buf,"get 1.txt")==0)//例子3{if(client_work3(sockfd)<0){perror("work3");return -1;}}else{if(client_work1(sockfd)<0)//例子1{perror("work1");return -1;}}memset(buf,0,64);}close(sockfd);return 0;       
} 
int  client_work1(int sockfd)
{char buf[64];int m=read(sockfd,buf,64);if(m<0){perror("read");return -1;}printf("字符串长度 %s\n",buf);return 0;
}
int  client_work2(int sockfd)
{char buf[64];int m=read(sockfd,buf,64);if(m<0){perror("read");return -1;}printf("时间 %s\n",buf);return 0;
}
int  client_work3(int sockfd)
{char buf[64]={0};int cp_fd=open("1.txt",O_RDWR|O_CREAT,0664);if(cp_fd<0){perror("open");return -1;}int m=-1;while((m=read(sockfd, buf, 64))>0){		write(cp_fd,buf,64);memset(buf,0,64);if(m<64)break;}printf("获取1.txt文件成功\n");close(cp_fd);return 0;
}

上述的程序并不能实现真正的双向通信,因为服务器的接收和发送是按照严格顺序进行,在接收数据阻塞时,无法实现服务器向客户端发送功能,客户端也是同理。为了能够让数据收发之间互不影响,实现双向通信的TCP客户/服务器,就需要借助多进程或者多线程的方式。以下为采用多线程的方式实现双向通信:

服务器:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>          
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <pthread.h>
void *send_data(void *arg);
int main(int argc, const char *argv[])
{//1.创建套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);if(sockfd < 0){perror("socket");return -1;}//端口复用函数,为解决端口号被系统占用的情况(如由强制关闭服务器引起)int on=1;int k=setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));if(k==-1){perror("setsockopt");return -1;}//2. 绑定服务器的IP地址和端口号struct sockaddr_in serveraddr = {0};serveraddr.sin_family = AF_INET;serveraddr.sin_port = htons(9999);//serveraddr.sin_addr.s_addr = inet_addr("0");serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);int len = sizeof(serveraddr);int ret = bind(sockfd,  (struct sockaddr *)&serveraddr, len );if(ret == -1){perror("bind");return -1;}//3.设置监听套接字ret = listen(sockfd, 10);if(ret == -1){perror("listen");return -1;}//4.等待客户端连接, 返回一个新的套接字(connfd:收发数据)//accept(sockfd, NULL, NULL);struct sockaddr_in clientaddr = {0};int n = sizeof(clientaddr);while(1)//循环服务器,解决客户端关闭导致服务器死循环或者直接退出的情况{printf("wait a client....\n");int connfd = accept(sockfd, (struct sockaddr *)&clientaddr, &n);if(connfd < 0){perror("accept");return -1;}printf("client ip:%s client port:%d\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));pthread_t tid;pthread_create(&tid,NULL,send_data,(void*)&connfd);//创建子线程,用于数据发送pthread_detach(tid);//收发数据char buf[64] = {0};while(1){int m = recv(connfd, buf, 64,0);if(m < 0){perror("read");return -1;}else if(m == 0) //客户端关闭{close(connfd);break;}printf("message:%s\n", buf);memset(buf, 0, 64); //数组清零}}close(sockfd);return 0;
}
void *send_data(void *arg)//服务器发送数据--子线程
{int nfd=*(int*)arg;char buf[1024]={0};while(1){fgets(buf,1024,stdin);buf[strlen(buf)-1]='\0';send(nfd,buf,strlen(buf),0);}close(nfd);
}

客户端:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>          
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <pthread.h>
void *recv_data(void *arg);
int main(int argc, const char *argv[])
{//1.创建套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);if(sockfd < 0){perror("socket");return -1;}//2. 连接服务器(IP,PORT)struct sockaddr_in serveraddr = {0};serveraddr.sin_family = AF_INET;serveraddr.sin_port = htons(9999);serveraddr.sin_addr.s_addr = inet_addr("0");//serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);int len = sizeof(serveraddr);int ret = connect(sockfd,  (struct sockaddr *)&serveraddr, len );if(ret == -1){perror("connect");return -1;}pthread_t tid;pthread_create(&tid,NULL,recv_data,(void*)&sockfd);//创建子线程,用于数据接收pthread_detach(tid);char buf[64] = {0};while(1){fgets(buf,64,stdin);buf[strlen(buf)-1]='\0';send(sockfd, buf, strlen(buf),0);memset(buf, 0, 64); //数组清零}close(sockfd);return 0;
}
void *recv_data(void *arg)
{int sockfd=*(int*)arg;char buf[1024]={0};while(1){int n=recv(sockfd,buf,sizeof(buf),0);if(n<=0){perror("read");return NULL;}printf("%s\n",buf);memset(buf,0,sizeof(buf));}
}

注意在运行可执行文件时要链接pthread库

例如:

gcc tcp_server.c -lpthread -o tcp_ser

gcc tcp_client.c -lpthread -o tcp_cli

这篇关于TCP服务器/客户端的Linux下的代码实现,单向通信、双向通信的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/507764

相关文章

python使用Akshare与Streamlit实现股票估值分析教程(图文代码)

《python使用Akshare与Streamlit实现股票估值分析教程(图文代码)》入职测试中的一道题,要求:从Akshare下载某一个股票近十年的财务报表包括,资产负债表,利润表,现金流量表,保存... 目录一、前言二、核心知识点梳理1、Akshare数据获取2、Pandas数据处理3、Matplotl

Linux线程同步/互斥过程详解

《Linux线程同步/互斥过程详解》文章讲解多线程并发访问导致竞态条件,需通过互斥锁、原子操作和条件变量实现线程安全与同步,分析死锁条件及避免方法,并介绍RAII封装技术提升资源管理效率... 目录01. 资源共享问题1.1 多线程并发访问1.2 临界区与临界资源1.3 锁的引入02. 多线程案例2.1 为

Django开发时如何避免频繁发送短信验证码(python图文代码)

《Django开发时如何避免频繁发送短信验证码(python图文代码)》Django开发时,为防止频繁发送验证码,后端需用Redis限制请求频率,结合管道技术提升效率,通过生产者消费者模式解耦业务逻辑... 目录避免频繁发送 验证码1. www.chinasem.cn避免频繁发送 验证码逻辑分析2. 避免频繁

分布式锁在Spring Boot应用中的实现过程

《分布式锁在SpringBoot应用中的实现过程》文章介绍在SpringBoot中通过自定义Lock注解、LockAspect切面和RedisLockUtils工具类实现分布式锁,确保多实例并发操作... 目录Lock注解LockASPect切面RedisLockUtils工具类总结在现代微服务架构中,分布

Java使用Thumbnailator库实现图片处理与压缩功能

《Java使用Thumbnailator库实现图片处理与压缩功能》Thumbnailator是高性能Java图像处理库,支持缩放、旋转、水印添加、裁剪及格式转换,提供易用API和性能优化,适合Web应... 目录1. 图片处理库Thumbnailator介绍2. 基本和指定大小图片缩放功能2.1 图片缩放的

精选20个好玩又实用的的Python实战项目(有图文代码)

《精选20个好玩又实用的的Python实战项目(有图文代码)》文章介绍了20个实用Python项目,涵盖游戏开发、工具应用、图像处理、机器学习等,使用Tkinter、PIL、OpenCV、Kivy等库... 目录① 猜字游戏② 闹钟③ 骰子模拟器④ 二维码⑤ 语言检测⑥ 加密和解密⑦ URL缩短⑧ 音乐播放

Python使用Tenacity一行代码实现自动重试详解

《Python使用Tenacity一行代码实现自动重试详解》tenacity是一个专为Python设计的通用重试库,它的核心理念就是用简单、清晰的方式,为任何可能失败的操作添加重试能力,下面我们就来看... 目录一切始于一个简单的 API 调用Tenacity 入门:一行代码实现优雅重试精细控制:让重试按我

Redis客户端连接机制的实现方案

《Redis客户端连接机制的实现方案》本文主要介绍了Redis客户端连接机制的实现方案,包括事件驱动模型、非阻塞I/O处理、连接池应用及配置优化,具有一定的参考价值,感兴趣的可以了解一下... 目录1. Redis连接模型概述2. 连接建立过程详解2.1 连php接初始化流程2.2 关键配置参数3. 最大连

Python实现网格交易策略的过程

《Python实现网格交易策略的过程》本文讲解Python网格交易策略,利用ccxt获取加密货币数据及backtrader回测,通过设定网格节点,低买高卖获利,适合震荡行情,下面跟我一起看看我们的第一... 网格交易是一种经典的量化交易策略,其核心思想是在价格上下预设多个“网格”,当价格触发特定网格时执行买

Oracle数据库定时备份脚本方式(Linux)

《Oracle数据库定时备份脚本方式(Linux)》文章介绍Oracle数据库自动备份方案,包含主机备份传输与备机解压导入流程,强调需提前全量删除原库数据避免报错,并需配置无密传输、定时任务及验证脚本... 目录说明主机脚本备机上自动导库脚本整个自动备份oracle数据库的过程(建议全程用root用户)总结