一、前言
手把手教你從0開(kāi)始編寫(xiě)TCP服務(wù)器程序,體驗(yàn)開(kāi)局一塊磚,大廈全靠壘。
為了避免篇幅過(guò)長(zhǎng)使讀者感到乏味,對(duì)【TCP服務(wù)器的開(kāi)發(fā)】進(jìn)行分階段實(shí)現(xiàn),一步步進(jìn)行優(yōu)化升級(jí)。本節(jié)在上一章節(jié)的基礎(chǔ)上,改為多進(jìn)程方式實(shí)現(xiàn)TCP服務(wù)器,為每個(gè)新接入的客戶(hù)端分配進(jìn)程,實(shí)現(xiàn)一個(gè)服務(wù)器程序處理多個(gè)客戶(hù)端連接。主要目的是比較不同方式的利弊關(guān)系。
二、新增使用的fork()函數(shù)
函數(shù)原型:
#include <unistd.h>
pid_t fork(void);
fork()通過(guò)復(fù)制調(diào)用進(jìn)程來(lái)創(chuàng)建一個(gè)新進(jìn)程。新進(jìn)程被稱(chēng)為子進(jìn)程。調(diào)用進(jìn)程被稱(chēng)為父進(jìn)程。
子進(jìn)程和父進(jìn)程在單獨(dú)的內(nèi)存空間中運(yùn)行。在執(zhí)行fork()時(shí),兩個(gè)內(nèi)存空間都具有相同的內(nèi)容。其中一個(gè)進(jìn)程執(zhí)行的內(nèi)存寫(xiě)入、文件映射(mmap)和取消映射(munmap),不會(huì)影響另一個(gè)進(jìn)程。
返回值:
- 返回0,代表子進(jìn)程。
- 返回非零,代表是父進(jìn)程。
三、實(shí)現(xiàn)步驟
使用多進(jìn)程方案,來(lái)一個(gè)連接請(qǐng)求則克隆一個(gè)子進(jìn)程。
(1)創(chuàng)建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)設(shè)置監(jiān)聽(tīng)。
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)為每個(gè)連接克隆子進(jìn)程。
pid_t pid=fork();
if(pid==0)
{
routine(clientfd);
break;
}
else
{
printf("pid = %d\n",pid);
}
(6)在子進(jìn)程里面接收數(shù)據(jù)。
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)在子進(jìn)程里面發(fā)送數(shù)據(jù)。
if(-1==send(clientfd,buf,ret,0))
{
printf("errno = %d, %s\n",errno,strerror(errno));
}
(8)關(guān)閉文件描述符。
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(int clientfd)
{
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);
pid_t pid=fork();
if(pid==0)
{
routine(clientfd);
break;
}
else
{
printf("pid = %d\n",pid);
}
}
close(listenfd);
return 0;
}
編譯:
gcc -o server server.c
五、TCP客戶(hù)端
5.1、自己實(shí)現(xiàn)一個(gè)TCP客戶(hù)端
自己實(shí)現(xiàn)一個(gè)TCP客戶(hù)端連接TCP服務(wù)器的代碼:
#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
5.2、Windows下可以使用NetAssist的網(wǎng)絡(luò)助手工具
下載地址:http://old.tpyboard.com/downloads/NetAssist.exe
小結(jié)
這里基于上一個(gè)章節(jié)的內(nèi)容進(jìn)行了升級(jí),可以接受多個(gè)客戶(hù)端同時(shí)連接,并為每個(gè)連接克隆一個(gè)子進(jìn)程進(jìn)行通信。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-762225.html
但是,使用多進(jìn)程會(huì)非常消耗資源,特別是高并發(fā)的時(shí)候,會(huì)是系統(tǒng)內(nèi)存爆滿(mǎn),開(kāi)銷(xiāo)巨大。那么有沒(méi)有辦法在一個(gè)線(xiàn)程中就可以完成高并發(fā)通信呢?答案是可以的,下一章節(jié)將介紹使用select實(shí)現(xiàn)一個(gè)線(xiàn)程完成多個(gè)連接的通信,也就是IO多路復(fù)用技術(shù)。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-762225.html
到了這里,關(guān)于TCP服務(wù)器的演變過(guò)程:多進(jìn)程實(shí)現(xiàn)一對(duì)多的TCP服務(wù)器的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!