一、TCP单连接版本
1.实现服务器与客户端之间的通信,客户端给服务器发送消息,服务器的作用是把客户端发给自己的消息再给客户端发送回去
服务器端
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<arpa/inet.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<netinet/in.h>
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
#define ERR_EXIT(m)\
do\
{\
perror(m);\
exit(EXIT_FAILURE);\
}while(0)
void Usage(){
printf("Usage: ./server [ip] [port]\n");
return;
}
int main(int argc,char*argv[])
{
if(3!=argc){
Usage();
return 1;
}
//1.创建socket
int sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd<0)
ERR_EXIT("socket");
sockaddr_in local;
local.sin_family=AF_INET;
local.sin_addr.s_addr=inet_addr(argv[1]);
local.sin_port=htons(atoi(argv[2]));
//2.将文件描述符(sockfd)和服务器的地址(ip+port)关联到一起
int ret=bind(sockfd,(sockaddr*)&local,sizeof(local));
if(ret<0)
ERR_EXIT("bind");
//3.监听,是服务器处于一种被动可以接受客户端请求的状态
if(listen(sockfd,5)<0)
ERR_EXIT("listen");
printf("bind and listen success,please accept......\n");
//4.循环的接受客户端的一个连接
while(1){
sockaddr_in client;
socklen_t len=sizeof(client);
int client_fd=accept(sockfd,(sockaddr*)&client,&len);
if(client_fd<0){
close(sockfd);
break;
}
printf("accept success!\n");
//5.循环的接收客户端发送消息,和给客户端发送消息
while(1){
char buf[1024]={0};
//读取客户端发送的消息
ssize_t read_size =read(client_fd,buf,sizeof(buf)-1);
if(read_size<0){
ERR_EXIT("read");
break;
}
if(read_size==0){
printf("client [%s:%d] quit!\n",\
inet_ntoa(client.sin_addr),\
ntohs(client.sin_port));
break;
}
buf[read_size]=0;
//输出客户端发送的消息
printf("client [%s:%d] say:> %s\n",
inet_ntoa(client.sin_addr),\
ntohs(client.sin_port),buf);
//给客户端回复消息,从键盘读入要发送的消息
printf("please enter:> ");
fflush(stdout);
memset(buf,0,sizeof(buf));
ssize_t s=read(0,buf,sizeof(buf)-1);
if(s<0){
ERR_EXIT("read");
break;
}
printf("please wait......\n");
write(client_fd,buf,strlen(buf));
}
close(client_fd);
}
close(sockfd);
return 0;
}
客户机端
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<string.h>
#define ERR_EXIT(m)\
do\
{\
perror(m);\
exit(EXIT_FAILURE);\
}while(0)
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
void Usage(){
printf("Usage: ./client [ip] [port]\n");
return ;
}
int main(int argc,char*argv[])
{
if(3!=argc){
Usage();
return 1;
}
//1.创建socket
int sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd<0)
ERR_EXIT("socket");
sockaddr_in server;
server.sin_family=AF_INET;
server.sin_addr.s_addr=inet_addr(argv[1]);
server.sin_port=htons(atoi(argv[2]));
//2.给服务器发送连接请求
int ret=connect(sockfd,(sockaddr*)&server,sizeof(server));
if(ret<0)
ERR_EXIT("connect");
printf("connect success!\n");
//3.循环的给服务器发送消息,和接收服务器发来的消息
socklen_t len=sizeof(server);
while(1){
char buf[1024]={0};
//从键盘读要发给服务器的消息
printf("please enter:> ");
fflush(stdout);
ssize_t s=read(0,buf,sizeof(buf)-1);
printf("please wait......\n");
if(s>0){
buf[s-1]=0;
//把读到的消息发给服务器
ssize_t write_size=write(sockfd,buf,strlen(buf));
//输出服务器发的消息
memset(buf,0,sizeof(buf));
ssize_t read_size=read(sockfd,buf,sizeof(buf)-1);
if(read_size>0)
{
buf[read_size]=0;
printf("server echo:> %s\n",buf);
}
}
}
close(sockfd);
return 0;
}
运行结果:
上面的程序有一个缺陷,那就是不能接受多个客户端的请求连接,因为服务器执行完accept之后,服务器就一直在while循环尝试read了,没有在继续调用accept了,从而导致服务器不能接受别的连接了
二、TCP的多个连接版本
为了解决上面所说的问题,那我们采用多个执行流把accept和read分开不就好了吗,一个执行流负责accept,另一个执行流负责read,这样就可以同时处理多个连接了,因为这两个执行流是并发执行的,负责执行accept的执行流它接受完一个客户端的连接之后,此时也可以去接受别的客户端的请求,而那个负责执行读写操作的执行流,不停的对多个客户端进行读写
2.使用多进程实现TCP通信
父进程负责不停地接受客户端的连接,即就是服务器执行accept,使用父进程在创建一个子进程,子进程负责执行读写操作
ps:因为子进程在退出时,会发送SIGCHLD信号,父进程需要把子进程的资源回收掉,否则会导致僵尸进程,现在回收子进程的资源有三种方式:
(1)
使用wait函数
,等待子进程执行完,wait是阻塞式的等待,父进程在等待子进程的时候,什么事也做不了,只能一直等待,那此时使用wait函数的话,还是实现不了多个客户端的连接,因为父进程执行accept接受一个请求之后,子进程在while循环中,一直在尝试读写,就不会再调用到accept函数了,此种方法不能处理多个客户端的连接请求
(2)使用waitpid函数
,设置选项WNOHANG,此时的话waitpid函数就是非阻塞式的等待了,但是会出现子进程还没执行完,父进程就已经执行结束了,还是会出现僵尸进程,如果采用轮询的方式,不停的去查看子进程的存在状态,一会看一下子进程有没有结束,从而回收子进程的资源,这样就会导致父进程可能不能及时的accept了,一心两用,不能保证质量哦!
(3)捕捉信号:
捕捉子进程结束后发送给父进程的SIGCHLD信号,把此信号给忽略掉,就不会出现僵尸进程了,也可以实现把accept和read分离开
服务器端
#include<stdio.h>
#include<stdlib.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<signal.h>
#define ERR_EXIT(m)\
do\
{\
perror(m);\
exit(EXIT_FAILURE);\
}while(0)
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
void Usage(){
printf("Usage: ./server [ip] [port]\n");
return;
}
void ProcessConnect(int client_fd,sockaddr_in client)
{
int pid=fork();
if(pid<0)
ERR_EXIT("fork");
else if(pid>0){
close(client_fd);
return;
}
else{
printf("\n");
//子进程进行循环的读写
while(1){
char buf[1024]={0};
ssize_t read_size=read(client_fd,buf,sizeof(buf)-1);
if(read_size<0){
perror("read");
close(client_fd);
break;
}
if(read_size==0){
printf("client[%s:%d] quit!\n",inet_ntoa(client.sin_addr),\
ntohs(client.sin_port));
close(client_fd);
exit(0);
}
buf[read_size]=0;
printf("client[%s:%d]say:> %s\n",inet_ntoa(client.sin_addr),\
ntohs(client.sin_port),buf);
printf("please enter:> ");
fflush(stdout);
ssize_t s=read(0,buf,sizeof(buf));
if(s<0)
ERR_EXIT("read");
if(s>0){
buf[s-1]=0;
write(client_fd,buf,strlen(buf));
}
printf("please wait......\n");
}
}
}
int main(int argc,char*argv[])
{
if(3!=argc){
Usage();
return 1;
}
signal(SIGCHLD,SIG_IGN);
int sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd<0)
ERR_EXIT("socket");
sockaddr_in local;
local.sin_family=AF_INET;
local.sin_addr.s_addr=inet_addr(argv[1]);
local.sin_port=htons(atoi(argv[2]));
int ret=bind(sockfd,(sockaddr*)&local,sizeof(local));
if(ret<0)
ERR_EXIT("bind");
if(listen(sockfd,5)<0)
ERR_EXIT("listen");
printf("bind and listen success,please accept......\n");
sockaddr_in client;
socklen_t len=sizeof(client);
while(1){
int client_fd=accept(sockfd,(sockaddr*)&client,&len);
if(client_fd<0){
perror("accept");
break;//跳出if,开始下一次的while
}
printf("accept succcess!\n");
ProcessConnect(client_fd,client);
}
close(sockfd);
return 0;
}
客户机端
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<string.h>
#define ERR_EXIT(m)\
do\
{\
perror(m);\
exit(EXIT_FAILURE);\
}while(0)
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
void Usage(){
printf("Usage: ./client [ip] [port]\n");
return ;
}
int main(int argc,char*argv[])
{
if(3!=argc){
Usage();
return 1;
}
//1.创建socket
int sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd<0)
ERR_EXIT("socket");
sockaddr_in server;
server.sin_family=AF_INET;
server.sin_addr.s_addr=inet_addr(argv[1]);
server.sin_port=htons(atoi(argv[2]));
//2.给服务器发送连接请求
int ret=connect(sockfd,(sockaddr*)&server,sizeof(server));
if(ret<0)
ERR_EXIT("connect");
printf("connect success!\n");
//3.循环的给服务器发送消息,和接收服务器发来的消息
socklen_t len=sizeof(server);
while(1){
char buf[1024]={0};
//从键盘读要发给服务器的消息
printf("please enter:> ");
fflush(stdout);
ssize_t s=read(0,buf,sizeof(buf)-1);
printf("please wait......\n");
if(s>0){
buf[s-1]=0;
//把读到的消息发给服务器
ssize_t write_size=write(sockfd,buf,strlen(buf));
//输出服务器发的消息
memset(buf,0,sizeof(buf));
ssize_t read_size=read(sockfd,buf,sizeof(buf)-1);
if(read_size>0)
{
buf[read_size]=0;
printf("server say:> %s\n",buf);
}
}
}
close(sockfd);
return 0;
}
运行结果:
ps:服务器的IP地址设为0,表示这服务器可以关联任一个地址
127.0.0.1是环回地址,是留给用户自己使用的,故客户端可以使用该地址
3.实现多线程版本的TCP通信,主线程负责accept(接受客户端发来的连接请求),新创建的线程负责循环的执行读写操作
服务器端
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<pthread.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#define ERR_EXIT(m)\
do\
{\
perror(m);\
exit(EXIT_FAILURE);\
}while(0)
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
//封装消息
typedef struct Arg
{
int fd;
sockaddr_in addr;
}Arg;
void*ThreadEntry(void*ptr)
{
Arg*arg=(Arg*)ptr;
while(1){
char buf[1024]={0};
ssize_t read_size=read(arg->fd,buf,sizeof(buf)-1);
if(read_size<0){
close(arg->fd);
perror("read");
break;
}
if(read_size==0){
printf("client [%s:%d] quit!\n",inet_ntoa(arg->addr.sin_addr),\
ntohs(arg->addr.sin_port));
close(arg->fd);
free(arg);
return NULL;
}
printf("client [%s:%d]:> %s\n",inet_ntoa(arg->addr.sin_addr),\
ntohs(arg->addr.sin_port),buf);
memset(buf,0,sizeof(buf));
printf("please enter:> ");
fflush(stdout);
ssize_t s=read(0,buf,sizeof(buf)-1);
if(s>0){
buf[s-1]=0;
write(arg->fd,buf,strlen(buf));
}
printf("please wait......\n");
}
return NULL;
}
void ProcessConnect(int client_fd,sockaddr_in client)
{
Arg* arg=(Arg*)malloc(sizeof(Arg));
arg->fd=client_fd;
arg->addr=client;
pthread_t tid;
pthread_create(&tid,NULL,ThreadEntry,(void*)arg);
pthread_detach(tid);
}
void Usage(){
printf("Usage: ./server [ip] [port]\n");
return;
}
int main(int argc,char*argv[])
{
if(3!=argc){
Usage();
return 1;
}
int sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd<0)
ERR_EXIT("socket");
sockaddr_in local;
local.sin_family=AF_INET;
local.sin_addr.s_addr=inet_addr(argv[1]);
local.sin_port=htons(atoi(argv[2]));
int ret=bind(sockfd,(sockaddr*)&local,sizeof(local));
if(ret<0)
ERR_EXIT("bind");
if(listen(sockfd,5)<0)
ERR_EXIT("listen");
printf("bind and listen success,please accept......\n");
sockaddr_in client;
socklen_t len=sizeof(client);
while(1){
int client_fd=accept(sockfd,(sockaddr*)&client,&len);
if(client_fd<0)
{
perror("accept");
break;
}
printf("accept success!\n");
ProcessConnect(client_fd,client);
}
close(sockfd);
return 0;
}
客户机端
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<string.h>
#define ERR_EXIT(m)\
do\
{\
perror(m);\
exit(EXIT_FAILURE);\
}while(0)
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
void Usage(){
printf("Usage: ./client [ip] [port]\n");
return ;
}
int main(int argc,char*argv[])
{
if(3!=argc){
Usage();
return 1;
}
//1.创建socket
int sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd<0)
ERR_EXIT("socket");
sockaddr_in server;
server.sin_family=AF_INET;
server.sin_addr.s_addr=inet_addr(argv[1]);
server.sin_port=htons(atoi(argv[2]));
//2.给服务器发送连接请求
int ret=connect(sockfd,(sockaddr*)&server,sizeof(server));
if(ret<0)
ERR_EXIT("connect");
printf("connect success!\n");
//3.循环的给服务器发送消息,和接收服务器发来的消息
socklen_t len=sizeof(server);
while(1){
char buf[1024]={0};
//从键盘读要发给服务器的消息
printf("please enter:> ");
fflush(stdout);
ssize_t s=read(0,buf,sizeof(buf)-1);
printf("please wait......\n");
if(s>0){
buf[s-1]=0;
//把读到的消息发给服务器
ssize_t write_size=write(sockfd,buf,strlen(buf));
//输出服务器发的消息
memset(buf,0,sizeof(buf));
ssize_t read_size=read(sockfd,buf,sizeof(buf)-1);
if(read_size>0)
{
buf[read_size]=0;
printf("server say:> %s\n",buf);
}
}
}
close(sockfd);
return 0;
}
运行结果:
相关命令
1.netsat -anp|grep +服务器的端口号//查看该服务器的信息
2.netstat -anp|grep -n2//显示查询出来的信息的格式
Local Address 本地IP和端口
Foreign Address 对端IP和端口
State:是当前进程的状态,LISTEN表示此进程处于监听状态,ESTABLISHED表示此进程已经建立连接
Recv-Q和Send-Q表示流量
0.0.0.0:*//是任何IP和端口,此种使用方法表示对任何IP和端口,都可以处理该IP和端口定义的套接字的通信
netstat -nt|grep 服务器的端口号//显示服务器中监听队列中的内容
相关函数的用法请参考博客:
https://blog.csdn.net/dangzhangjing97/article/details/80280770
IP地址和端口请参考博客:
https://blog.csdn.net/dangzhangjing97/article/details/80280254
关于网络字节序与主机字节序之间转换的相关函数:
https://blog.csdn.net/dangzhangjing97/article/details/80303625
三.使用TCP实现一个加法器
服务器负责计算客户端发送的数据,客户端再把结果输出到屏幕上
#include "proto.h"
void Usage(){
printf("Usage:./server [ip] [port]\n");
return;
}
int main(int argc,char*argv[])
{
if(argc!=3){
Usage();
return 1;
}
//创建一个socket文件描述符
int sockfd=socket(AF_INET,SOCK_STREAM,0);
//if(sockfd<0)
// ERR_EXIT("socket");
sockaddr_in local;
local.sin_family=AF_INET;
local.sin_addr.s_addr=inet_addr(argv[1]);
local.sin_port=htons(atoi(argv[2]));
Response response;
Request request;
//将文件描述符和服务器的地址(ip+port)关联到一起
if(bind(sockfd,(sockaddr*)&local,sizeof(local))<0)
ERR_EXIT("bind");
//将服务器转换成被动状态,以便于接受客户端发送的请求连接
if(listen(sockfd,5)<0)
ERR_EXIT("listen");
printf("bind and listen success,wait accept......\n");
sockaddr_in client;
socklen_t len=sizeof(client);
//循环的接受客户端的连接请求,完成客户端的需求
while(1){
int client_fd=accept(sockfd,(sockaddr*)&client,&len);
if(client_fd<0){
close(client_fd);
break;//重新开始下一次的接收来自客户端的连接
}
//此时已经和客户端建立连接,循环的与客户端发送消息
while(1){
//读客户端需要求和的数字
ssize_t read_size=read(client_fd,&request,sizeof(request));
if(read_size>0){
response.sum=request.a+request.b;
}
//把计算好的结果发给客户端
write(client_fd,&response,sizeof(response));
}
}
close(sockfd);
return 0;
}
客户机端
#include"proto.h"
void Usage(){
printf("Usage ./client [ip] [port]\n");
return;
}
int main(int argc,char*argv[])
{
if(3!=argc){
Usage();
return 1;
}
int sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd<0)
ERR_EXIT("socket");
sockaddr_in server;
server.sin_family=AF_INET;
server.sin_addr.s_addr=inet_addr(argv[1]);
server.sin_port=htons(atoi(argv[2]));
Request request;
Response response;
socklen_t len=sizeof(server);
if(connect(sockfd,(sockaddr*)&server,len)<0)
ERR_EXIT("connect");
while(1){
char buf[1024]={0};
printf("请输入需要求和的两个数:> ");
fflush(stdout);
scanf("%d %d",&request.a,&request.b);
//把需要求和的两个数发给服务器(服务器是计算结果的)
write(sockfd,&request,sizeof(request));
//读取服务器计算的结果
ssize_t s=read(sockfd,&response,sizeof(response));
if(s>0){
printf("结果为:> %d\n",response.sum);
}
}
close(sockfd);
return 0;
}
运行结果:
结果解析:服务器负责计算结果,并把计算的结果发给客户机,客户机把自己想要计算的数据发给服务器就好,然后接收服务器的计算结果就OK,最后把计算的结果输出到屏幕上