首先了解TCP服务端与客户端设计流程
tcp的服务端的设计流程:
1.建立套接字socket
2.绑定ip地址与端口号
3.建立监听队列
4.接受连接,产生新的套接字
5.数据的发送和接受
具体代码如下:
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
int main(int argc , char *argv[])
{
int sockfd,ret,cockfd;
char buf[128] = {0};
ssize_t recv_bytes,secv_bytes;
if(argc < 3)
{
fprintf(stderr,"usage: %s [IP] [PORT]\n",argv[0]);
return -1;
}
//定义地址结构体大小
struct sockaddr_in src,dest;
socklen_t len = sizeof(src);
socklen_t addrlen = sizeof(dest);
//建立套接字
sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd == -1)
{
perror("socket fail");
return -1;
}
//绑定
src.sin_family = AF_INET;
src.sin_port = htons(atoi(argv[2]));
src.sin_addr.s_addr = inet_addr(argv[1]);
//绑定套接字
ret = bind(sockfd,(const struct sockaddr *)&src,len);
if(ret == -1)
{
perror("bind");
return -1;
}
//监听队列,监听连接请求,128为未决队列长度
ret = listen(sockfd,128);
if(ret == -1)
{
perror("listen");
return -1;
}
//接受客户端连接请求,产生连接套接字,用于和客户端通信,通过dest保存客户端ip地址和port口号
cockfd = accept(sockfd,(struct sockaddr *)&dest,&addrlen);
if(cockfd == -1)
{
perror("accept");
return -1;
}
//打印客户端ip和port,需要将网络字节序转换为主机字节序
printf("client IP :%s PORT :%d\n",inet_ntoa(dest.sin_addr),ntohs(dest.sin_port));
while(1)
{
memset(buf,0,sizeof(buf));
//接收客户端的信息储存在buf中
//recv_bytes = recv(cockfd,buf,sizeof(buf),0);
//recv_bytes = read(cockfd,buf,sizeof(buf));
recv_bytes = recvfrom(cockfd,buf,sizeof(buf),0,(struct sockaddr *)&dest,&addrlen);
if(recv_bytes == -1)
{
perror("recv fail");
return -1;
}else if(recv_bytes == 0)//客户端退出返回0,服务器这里也退出
{
printf("client shutdown\n");
break;
}
//secv_bytes = send(cockfd,buf,sizeof(buf),0);
//secv_bytes = write(cockfd,buf,sizeof(buf));
secv_bytes = sendto(cockfd,buf,sizeof(buf),0,(struct sockaddr *)&dest,addrlen);
if(secv_bytes == -1)
{
perror("secv fail");
return -1;
}
printf("buf : %s\n",buf);
}
}
tcp客户端的设计流程:
1.建立套接字
2.连接服务器
3.数据读写
具体代码如下:
服务器与客户端的设计流程相差无几,客户端仅仅是多了个连接服务器,少了监听套接字和接受客户端两个步骤。
连接客户端:
ret = connect(sockfd,(const struct sockaddr *)&dest,len);
if(ret == -1)
{
perror("connect fail");
return -1;
}
而多线程的TCP服务器可以连接多个客户端请求
此时我们需要在主函数定义一个线程:
只需在TCP服务器代码上稍微改动即可
pthread_t tid;
之后,需要在连接客户端的代码前加一个while循环作为父进程,在父进程中定义pthread_detach()来回收子进程。(防止子进程过多占有资源)
代码如下:
while(1)
{
cockfd = accept(sockfd,(struct sockaddr *)&dest,&addrlen);
if(cockfd == -1)
{
perror("accept");
return -1;
}
//打印客户端ip和port,需要将网络字节序转换为主机字节序
printf("client IP :%s PORT :%d\n",inet_ntoa(dest.sin_addr),ntohs(dest.sin_port));
num = pthread_create(&tid,NULL,thread_exec,&cockfd);
if(num != 0)
{
errno = num;
perror("pthread_create fail");
return -1;
}
pthread_detach(tid);
}
最后把数据的读写放在子线程中,遍可实现多个客户端的请求。
代码如下:
void *thread_exec(void *arg)
{
int cockfd = *(int *)arg;
char buf[128] = {0};
ssize_t recv_bytes,secv_bytes;
//struct sockaddr_in dest;
//socklen_t addrlen = sizeof(dest);
while(1)
{
memset(buf,0,sizeof(buf));
//接收客户端的信息储存在buf中
recv_bytes = recv(cockfd,buf,sizeof(buf),0);
//recv_bytes = read(cockfd,buf,sizeof(buf));
//recv_bytes = recvfrom(cockfd,buf,sizeof(buf),0,(struct sockaddr *)&dest,&addrlen);
if(recv_bytes == -1)
{
perror("recv fail");
break;
}else if(recv_bytes == 0)//客户端退出返回0,服务器这里也退出
{
printf("client shutdown\n");
break;
}
secv_bytes = send(cockfd,buf,sizeof(buf),0);
//secv_bytes = write(cockfd,buf,sizeof(buf));
// secv_bytes = sendto(cockfd,buf,sizeof(buf),0,(struct sockaddr *)&dest,addrlen);
if(secv_bytes == -1)
{
perror("secv fail");
break;
}
printf("buf : %s\n",buf);
}
pthread_exit(NULL);
}
此时注意我们的函数类型为 void *型,在对判断错误的过程中,不能使用return -1来进行返回,运行后会报警告。