手把手教你从0开始编写TCP服务器程序,体验开局一块砖,大厦全靠垒。
为了避免篇幅过长使读者感到乏味,对【TCP服务器的开发】进行分阶段实现,一步步进行优化升级。本节在上一章节的基础上,添加多线程,为每个新接入的客户端分配线程,实现一个服务器程序处理多个客户端连接。
pthread_create函数原型:
#include <pthread.h>
int pthread_create(
pthread_t *restrict tidp, //新创建的线程ID指向的内存单元。
const pthread_attr_t *restrict attr, //线程属性
void *(*start_rtn)(void *), //线程函数的地址
void *restrict arg //线程函数所需的参数
);
函数pthread_exit()
终止调用线程,并通过retval返回一个值,该值(如果线程是可连接的)可用于调用pthread_join()
的同一进程中的另一个线程。
pthread_cleanup_push()
建立的任何尚未弹出的清理处理程序都会弹出(按与推送顺序相反的顺序)并执行。如果线程有任何特定于线程的数据,那么在执行清理处理程序之后,将以未指定的顺序调用相应的析构函数。
当线程终止时,进程共享资源(例如,互斥锁、条件变量、信号量和文件描述符)不会被释放,并且使用atexit()注册的函数也不会被调用。
在进程中的最后一个线程终止后,进程终止为调用atexit()
,出口状态为零;因此,进程共享资源被释放,并且使用atexit()
注册的函数被调用。
函数原型:
#include <pthread.h>
void pthread_exit(void *retval);
使用多线程方案,来一个连接请求则创建一个线程。
(1)创建socket。
int listenfd=socket(AF_INET,SOCK_STREAM,0);
if(listenfd==-1){
printf("errno = %d, %s\n",errno,strerror(errno));
return SOCKET_CREATE_FAILED;
}
(2)绑定地址。
struct sockaddr_in server;
memset(&server,0,sizeof(server));
server.sin_family=AF_INET;
server.sin_addr.s_addr=htonl(INADDR_ANY);
server.sin_port=htons(LISTEN_PORT);
if(-1==bind(listenfd,(struct sockaddr*)&server,sizeof(server))){
printf("errno = %d, %s\n",errno,strerror(errno));
close(listenfd);
return SOCKET_BIND_FAILED;
}
(3)设置监听。
if(-1==listen(listenfd,BLOCK_SIZE)){
printf("errno = %d, %s\n",errno,strerror(errno));
close(listenfd);
return SOCKET_LISTEN_FAILED;
}
(4)接收连接。
struct sockaddr_in client;
memset(&client,0,sizeof(client));
socklen_t len=sizeof(client);
int clientfd=accept(listenfd,(struct sockaddr*)&client,&len);
if(clientfd==-1){
printf("errno = %d, %s\n",errno,strerror(errno));
close(listenfd);
return SOCKET_ACCEPT_FAILED;
}
(5)为每个连接创建线程。
pthread_t thread;
if(0!=pthread_create(&thread,NULL,routine,&clientfd))
{
printf("pthread_create failed");
}
(6)在线程函数里面接收数据。
char buf[BUFFER_LENGTH]={0};
ret=recv(clientfd,buf,BUFFER_LENGTH,0);
if(ret==0) {
printf("connection dropped\n");
}
printf("recv --> %s\n",buf);
(7)在线程函数里面发送数据。
if(-1==send(clientfd,buf,ret,0))
{
printf("errno = %d, %s\n",errno,strerror(errno));
}
(8)关闭文件描述符。
close(listenfd);
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#define LISTEN_PORT 8888
#define BLOCK_SIZE 10
#define BUFFER_LENGTH 1024
enum ERROR_CODE{
SOCKET_CREATE_FAILED=-1,
SOCKET_BIND_FAILED=-2,
SOCKET_LISTEN_FAILED=-3,
SOCKET_ACCEPT_FAILED=-4
};
void *routine(void *arg)
{
int clientfd=*((int*)arg);
while(1)
{
// 5.
char buf[BUFFER_LENGTH]={0};
int ret=recv(clientfd,buf,BUFFER_LENGTH,0);
if(ret==0) {
printf("connection dropped\n");
break;
}
printf("fd=%d recv --> %s\n",clientfd,buf);
send(clientfd,buf,ret,0);
}
close(clientfd);
}
int main(int argc,char **argv)
{
// 1.
int listenfd=socket(AF_INET,SOCK_STREAM,0);
if(listenfd==-1){
printf("errno = %d, %s\n",errno,strerror(errno));
return SOCKET_CREATE_FAILED;
}
// 2.
struct sockaddr_in server;
memset(&server,0,sizeof(server));
server.sin_family=AF_INET;
server.sin_addr.s_addr=htonl(INADDR_ANY);
server.sin_port=htons(LISTEN_PORT);
if(-1==bind(listenfd,(struct sockaddr*)&server,sizeof(server))){
printf("errno = %d, %s\n",errno,strerror(errno));
close(listenfd);
return SOCKET_BIND_FAILED;
}
// 3.
if(-1==listen(listenfd,BLOCK_SIZE)){
printf("errno = %d, %s\n",errno,strerror(errno));
close(listenfd);
return SOCKET_LISTEN_FAILED;
}
printf("listen port: %d\n",LISTEN_PORT);
struct sockaddr_in client;
socklen_t len=sizeof(client);
int clientfd=-1;
while(1)
{
// 4.
memset(&client,0,sizeof(client));
clientfd=accept(listenfd,(struct sockaddr*)&client,&len);
if(clientfd==-1){
printf("errno = %d, %s\n",errno,strerror(errno));
continue;
}
printf("accept successdul, fd = %d\n",clientfd);
pthread_t thread;
if(0!=pthread_create(&thread,NULL,routine,&clientfd))
{
printf("pthread_create failed");
break;
}
}
close(listenfd);
return 0;
}
编译:
gcc -o server server.c -lpthread
注意,由于使用了多线程,在编译时要加上 -lpthread 连接多线程库。
自己实现一个TCP客户端连接TCP服务器的代码,主要是连接服务器,发送数据、接收数据功能:
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#define BUFFER_LENGTH 1024
enum ERROR_CODE{
SOCKET_CREATE_FAILED=-1,
SOCKET_CONN_FAILED=-2,
SOCKET_LISTEN_FAILED=-3,
SOCKET_ACCEPT_FAILED=-4
};
int main(int argc,char** argv)
{
if(argc<3)
{
printf("Please enter the server IP and port.");
return 0;
}
printf("connect to %s, port=%s\n",argv[1],argv[2]);
int connfd=socket(AF_INET,SOCK_STREAM,0);
if(connfd==-1)
{
printf("errno = %d, %s\n",errno,strerror(errno));
return SOCKET_CREATE_FAILED;
}
struct sockaddr_in serv;
serv.sin_family=AF_INET;
serv.sin_addr.s_addr=inet_addr(argv[1]);
serv.sin_port=htons(atoi(argv[2]));
socklen_t len=sizeof(serv);
int rwfd=connect(connfd,(struct sockaddr*)&serv,len);
if(rwfd==-1)
{
printf("errno = %d, %s\n",errno,strerror(errno));
close(rwfd);
return SOCKET_CONN_FAILED;
}
int ret=1;
while(ret>0)
{
char buf[BUFFER_LENGTH]={0};
printf("Please enter the string to send:\n");
scanf("%s",buf);
send(connfd,buf,strlen(buf),0);
memset(buf,0,BUFFER_LENGTH);
printf("recv:\n");
ret=recv(connfd,buf,BUFFER_LENGTH,0);
printf("%s\n",buf);
}
close(rwfd);
return 0;
}
编译:
gcc -o client client.c
下载地址:http://old.tpyboard.com/downloads/NetAssist.exe
这里基于上一个章节的内容进行了升级,由原来的一对一连接通信升级为一对多连接通信,可以接受多个客户端同时连接,并为每个连接分配一个线程。
但是,使用多线程会有一个瓶颈,受CPU的核心数和内存大小线程,一台机器可以并发的线程数量是有限的,内存大小也是有限的;这个模式下1024个线程基本就是一台服务器机器的极限。
除了可以使用多线程实现一对多发服务外,还可以使用多进程来实现这个功能。这个我们将在下一个章节进行实现。