C语言多线程服务器的实现实例
本文基于C标准库提供的网络通信API,使用TCP,实现一个简单的多线程服务器Demo。
首先要看API
API
字节序转换
函数原型:
#includeuint64_thtonll(uint64_thostlonglong); uint32_thtonl(uint32_thostlong); uint16_thtons(uint16_thostshort); uint64_tntohll(uint64_tnetlonglong); uint32_tntohl(uint32_tnetlong); uint16_tntohs(uint16_tnetshort);
h表示host,n表示network,这些函数的作用是把主机的字节序转换为网络的字节序(即小端到大端的转变)。
例如:
#include#include intmain() { uint32_thost=0x01020304;//high->low:01020304 uint32_tnetwork=htonl(host);//high->low:04030201 printf("%p\n",network);//0x4030201 }
socket
函数原型:
#includeintsocket(intdomain,inttype,intprotocol);
建立一个协议族为domain,协议类型为type,协议编号为protocol的套接字文件描述符。如果函数调用成功,会返回一个标识这个套接字的文件描述符,失败的时候返回-1。
domain的取值:
NamePurposeManpage AF_UNIX,AF_LOCALLocalcommunicationunix(7) AF_INETIPv4Internetprotocolsip(7) AF_INET6IPv6Internetprotocolsipv6(7) AF_IPXIPX-Novellprotocols AF_NETLINKKerneluserinterfacedevicenetlink(7) AF_X25ITU-TX.25/ISO-8208protocolx25(7) AF_AX25AmateurradioAX.25protocol AF_ATMPVCAccesstorawATMPVCs AF_APPLETALKAppleTalkddp(7) AF_PACKETLowlevelpacketinterfacepacket(7) AF_ALGInterfacetokernelcryptoAPI
AF是AddressFamily的缩写,INET是Internet的缩写。某些地方可能会使用PF,即ProtocolFamily,应该是同一个东西。
type的取值:
SOCK_STREAMProvidessequenced,reliable,two-way,connection-basedbytestreams.Anout-of-banddatatransmissionmechanismmaybesupported. SOCK_DGRAMSupportsdatagrams(connectionless,unreliablemessagesofafixedmaximumlength). SOCK_SEQPACKETProvidesasequenced,reliable,two-wayconnection-baseddatatransmissionpathfordatagramsoffixedmaximumlength;aconsumerisrequiredtoreadanentirepacketwitheachinputsystemcall. SOCK_RAWProvidesrawnetworkprotocolaccess. SOCK_RDMProvidesareliabledatagramlayerthatdoesnotguaranteeordering. SOCK_PACKETObsoleteandshouldnotbeusedinnewprograms;seepacket(7).
type常用的是STREAM和DGRAM,根据描述,可以确定前者对应TCP,而后者对应UDP:
- SOCK_STREAM套接字表示一个双向的字节流,与管道类似。流式的套接字在进行数据收发之前必须已经连接,连接使用connect()函数进行。一旦连接,可以使用read()或者write()函数进行数据的传输,流式通信方式保证数据不会丢失或者重复接收。
- SOCK_DGRAM和SOCK_RAW这个两种套接字可以使用函数sendto()来发送数据,使用recvfrom()函数接受数据,recvfrom()接受来自制定IP地址的发送方的数据。
对于第3个参数protocal,用于指定某个协议的特定类型,即type类型中的某个类型。通常某协议中只有一种特定类型,这样protocol参数仅能设置为0;但是有些协议有多种特定的类型,就需要设置这个参数来选择特定的类型。
bind
函数原型:
intbind(intsockfd,conststructsockaddr*addr,socklen_taddrlen);
如果函数执行成功,返回值为0,否则为SOCKET_ERROR。
参数:
- sockfd是一个有效的socket描述符(函数socket()的有效返回值)。
- addrlen是第二个参数addr结构体的长度。
- addr是一个sockaddr结构体指针,包含IP和端口等信息。
sockaddr的结构如下:
structsockaddr{ sa_family_tsa_family; charsa_data[14]; }; //sa_familt_t是无符号整型,Ubuntu下是unsignedshortint
sockaddr的存在是为了统一地址结构的表示方法,统一接口函数,使得不同的地址结构可以被bind(),connect(),recvfrom(),sendto()等函数调用。但一般的编程中并不直接对此数据结构进行操作,而使用另一个与之等价的数据结构sockaddr_in:
structsockaddr_in{ shortintsin_family;/*Addressfamily*/ unsignedshortintsin_port;/*Portnumber*/ structin_addrsin_addr;/*Internetaddress*/ unsignedcharsin_zero[8];/*Samesizeasstructsockaddr*/ };
各字段解析:
- sin_family:指代协议族,在socket编程中有3个取值AF_INET,AF_INET6,AF_UNSPEC.
- sin_port:存储端口号(使用网络字节顺序)
- sin_addr:存储IP地址,使用in_addr这个数据结构
- sin_zero:是为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节。
in_addr的结构如下:
typedefuint32_tin_addr_t; structin_addr{ in_addr_ts_addr; };
listen
intlisten(intsockfd,intbacklog);
返回值:无错误,返回0,否则-1。
作用:listen函数使用主动连接套接字变为被连接套接口,使得一个进程可以接受其它进程的请求,从而成为一个服务器进程。在TCP服务器编程中listen函数把进程变为一个服务器,并指定相应的套接字变为被动连接。
listen函数一般在调用bind之后,调用accept之前调用。
backlog参数指定连接请求队列的最大个数。
accept
intaccept(intsockfd,structsockaddr*addr,socklen_t*addrlen);
接受连接请求,成功返回一个新的套接字描述符newfd,失败返回-1。返回值newfd与参数sockfd是不同的,newfd专门用于与客户端的通信,而sockfd是专门用于listen的socket。
addr和addrlen都是指针,用于接收来自客户端的addr的信息。
inet_addr
函数原型:
in_addr_tinet_addr(constchar*cp);
将一个点分十进制的IP字符串转换为网络字节序的uint32_t。
例子
intmain() { constchar*ip="127.0.0.1";//7f.00.00.01 printf("%p\n",inet_addr(ip));//0x0100007f }
send
#include#include ssize_tsend(intsockfd,constvoid*buf,size_tlen,intflags); ssize_tsendto(intsockfd,constvoid*buf,size_tlen,intflags, conststructsockaddr*dest_addr,socklen_taddrlen); ssize_tsendmsg(intsockfd,conststructmsghdr*msg,intflags);
其中send(fd,buf,len,flags)与sendto(fd,buf,len,flags,NULL,0)等价。
recv
#include#include ssize_trecv(intsockfd,void*buf,size_tlen,intflags); ssize_trecvfrom(intsockfd,void*buf,size_tlen,intflags, structsockaddr*src_addr,socklen_t*addrlen); ssize_trecvmsg(intsockfd,structmsghdr*msg,intflags);
其中recv(fd,buf,len,flags)与recvfrom(fd,buf,len,flags,NULL,0)等价。
connect
intconnect(intsockfd,conststructsockaddr*addr,socklen_taddrlen);
成功返回0,失败返回-1。
sockfd是客户端进程创建的,用于与服务端通信的socket;addr是目标服务器的IP地址和端口。
多线程服务器
本次实现的场景如下:
- 客户端可以具有多个,客户端主动连接服务器,允许每个客户端发送msg到服务器,并接受来自服务器的信息。
- 服务端对于每个申请连接到客户端,创建一个线程处理请求。对于客户端发送过来的msg,然后服务器把msg加上一些其他字符串,发送回客户端。
server
#include#include #include #include #include #include #include #include #include #definePORT8887 #defineQUEUE10 constchar*pattern="Hello,Iamtheserver.Yourmsgisreceived,whichis:%s"; typedefstruct { structsockaddr_inaddr; socklen_taddr_len; intconnectfd; }thread_args; void*handle_thread(void*arg) { thread_args*targs=(thread_args*)arg; pthread_ttid=pthread_self(); printf("tid=%uandsocket=%d\n",tid,targs->connectfd); charsend_buf[BUFSIZ]={0},recv_buf[BUFSIZ]={0}; while(1) { intlen=recv(targs->connectfd,recv_buf,BUFSIZ,0); printf("[Client%d]%s",targs->connectfd,recv_buf); if(strcmp("q\n",recv_buf)==0) break; sprintf(send_buf,pattern,recv_buf); send(targs->connectfd,send_buf,strlen(send_buf),0); memset(send_buf,0,BUFSIZ),memset(recv_buf,0,BUFSIZ); } close(targs->connectfd); free(targs); pthread_exit(NULL); } intmain() { intlistenfd=socket(AF_INET,SOCK_STREAM,0); printf("serverislisteningatsocketfd=%d\n",listenfd); structsockaddr_inaddr; addr.sin_family=AF_INET; addr.sin_port=htons(PORT); addr.sin_addr.s_addr=htonl(INADDR_ANY); if(bind(listenfd,(structsockaddr*)&addr,sizeof(addr))==-1) { perror("binderror\n"); exit(-1); } if(listen(listenfd,QUEUE)==-1) { perror("listenerror\n"); exit(-1); } while(1) { thread_args*targs=malloc(sizeof(thread_args)); targs->connectfd=accept(listenfd,(structsockaddr*)&targs->addr,&targs->addr_len); //intnewfd=accept(sockfd,NULL,NULL); pthread_ttid; pthread_create(&tid,NULL,handle_thread,(void*)targs); pthread_detach(tid); } close(listenfd); }
client
#include#include #include #include #include #include #include #include #definePORT8887 constchar*target_ip="127.0.0.1"; intmain() { intsockfd=socket(AF_INET,SOCK_STREAM,0); printf("clientsocket=%d\n",sockfd); structsockaddr_inaddr; addr.sin_family=AF_INET; addr.sin_port=htons(PORT); addr.sin_addr.s_addr=inet_addr(target_ip); if(connect(sockfd,(structsockaddr*)&addr,sizeof(structsockaddr_in))<0) { perror("connecterror\n"); exit(-1); } charsend_buf[BUFSIZ],recv_buf[BUFSIZ]; while(fgets(send_buf,BUFSIZ,stdin)!=NULL) { if(strcmp(send_buf,"q\n")==0) break; send(sockfd,send_buf,strlen(send_buf),0); printf("[Client]%s\n",send_buf); recv(sockfd,recv_buf,BUFSIZ,0); printf("[Server]%s\n",recv_buf); memset(send_buf,0,BUFSIZ),memset(recv_buf,0,BUFSIZ); } close(sockfd); exit(0); }
运行结果
编译:
gccserver.c-oserver-lpthread
gccclient.c-oclient
先运行server,后运行多个client.
需要注意的是,这里的服务器,客户端都是运行在同一机器上的,所以客户端使用的目标IP是127.0.0.1,如果想进一步更全面地测试,应该把服务端运行在一个云服务器上,然后开放8887端口,再进行测试。
到此这篇关于C语言多线程服务器的实现实例的文章就介绍到这了,更多相关C语言多线程服务器的实现内容请搜索毛票票以前的文章或继续浏览下面的相关文章希望大家以后多多支持毛票票!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。