在TCP/IP協議中,“IP地址+TCP或UDP端口號”唯一標識網絡通訊中的一個進程,“IP地址+端口號”稱爲(套接字)socket。在TCP協議中,建立連接的兩個進程各自有一個socket來標識,這兩個socket組成的socket pair就唯一標識一個連接。
TCP/IP協議最早在BSD UNIX上實現,爲TCP/IP協議設計的應用層編程接口稱爲socket API。
此處使用socket API應用層編程接口來實現多進程多線程的TCP服務器。
接口函數如下:
- 創建套接字
參數:
domain:有以下參數可選IPv4使用AF_INET
type:服務的類型:TCP爲SOCK_STREAM,UDP爲SOCK_DRAM
protocol:一般默認爲0.
返回值:成功返回文件描述符,失敗-1. - 套接字綁定:填充網絡
參數:
sockfd:創建的文件描述符(套接字)
addrlen:結構體長度
addr:結構體類型指針,其結構體爲(在傳參時需強制類型轉換):
struct sockaddr_in{
sin_family; //IPv4爲AF_INET
sin_port;//端口號
sin_addr;//IP地址
sin_pad;//填充字段
};
套接字監聽,是否有other連接自己
參數:
sockfd:套接字文件描述符;
backlog:一般數值不能過大,此處代碼設爲10.
返回值:成功則將套接字改爲了監聽套接字- 接收other主機連接,保存連接自己主機的信息
參數:
sockfd:監聽套接字
addr:輸出型參數,結構體保存客戶端套接字信息
addrlen:輸入輸出型參數,傳入與傳出結構體大小.
返回值:成功返回一個新的套接字,真正用於服務通信等.- 發起連接請求
參數:
sockfd:套接字文件描述符
addr:連接的服務器socket信息;
addrlen:結構體長度.
以上即爲實現TCP服務器的主要函數:代碼如下
tcp_server.c:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
//./tcp_server 192.168.x.x 8080
static void usage(const char* proc)
{
printf("usage:%s [server_ip] [server_port]\n",proc);
}
int startup(char* ip,int port)
{
int sock=socket(AF_INET,SOCK_STREAM,0); //創建套接字
if(sock<0)
{
perror("socket");
close(sock);
exit(2);
}
struct sockaddr_in server;
server.sin_family=AF_INET;
server.sin_port=htons(port);
server.sin_addr.s_addr=inet_addr(ip);
if(bind(sock,(struct sockaddr*)&server,sizeof(server))<0) //綁定:填充網絡
{
perror("bind");
close(sock);
exit(3);
}
if(listen(sock,10)<0) //監聽
{
perror("listen");
close(sock);
exit(4);
}
return sock; //返回監聽套接字
}
void* request(void* arg)
{
int new_sock=(int)arg;
while(1)
{
char buf[1024];
ssize_t s=read(new_sock,buf,sizeof(buf)-1);
if(s>0)
{
buf[s]=0;
printf("get new client# %s\n",buf);
write(new_sock,buf,strlen(buf));
}
else if(s==0)
{
printf("client close!!!\n");
break;
}
else
{
perror("read");
break;
}
}
return (void*)0;
}
int main(int argc,char* argv[])
{
if(argc != 3) //命令行用法
{
usage(argv[0]);
return 1;
}
int listen_sock=startup(argv[1],atoi(argv[2])); //創建監聽套接字:函數三步
while(1)
{
struct sockaddr_in client;
socklen_t addrlen=sizeof(client);
int new_sock=accept(listen_sock,(struct sockaddr*)&client,&addrlen); //接收客戶端信息進行通信
if(new_sock<0)
{
perror("accept");
continue;
}
//version 1.3
pthread_t id;
pthread_create(&id,NULL,request,(void*)new_sock); //主線程監聽接收客戶端,創建線程爲每一個客戶端服務
pthread_detach(id); //使線程分離,主線程不用阻塞等待,操作系統去回收
//version 1.2
/*pid_t id=fork(); //即每有一個客戶端連接則創建一個子進程爲其服務
if(id<0)
{
perror("fork");
close(new_sock);
}
else if(id==0) //child 子進程進行處理請求,服務
{
close(listen_sock);
pid_t _id=fork(); //在子進程再次fork,使子進程的子進程去執行服務
if(_id>0)
{
exit(0); //子進程退出,即讓父進程不會阻塞等待
}
else if(_id==0) //child->child
{
while(1)
{
char buf[1024];
ssize_t s=read(new_sock,buf,sizeof(buf)-1);
if(s>0)
{
buf[s]=0;
printf("[%s:%d]# %s\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port),buf);
write(new_sock,buf,strlen(buf));
}
else if(s==0)
{
printf("client close!!!\n");
break;
}
else
{
perror("read");
break;
}
}
}
close(new_sock);
exit(0);
}
else //father 父進程進行監聽接收多個客戶端
{
close(new_sock);
waitpid(id,NULL,0);
}*/
/*verison 1.1
//服務:
printf("get new client [%s:%d]\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port)); //接收成功
while(1)
{
char buf[1024];
//先從網絡上讀取客戶端請求
ssize_t s=read(new_sock,buf,sizeof(buf)-1);
if(s>0)
{
buf[s]=0;
printf("client# %s\n",buf);
//向客戶端服務:寫入網絡
write(new_sock,buf,strlen(buf));
}
else if(s==0) //若讀取爲0字節,則客戶端關閉斷開連接,則不在服務,再次去監聽接收
{
printf("client close!!!\n");
break;
}
else
{
perror("read");
break;
}
}*/
}
return 0;
}
tcp_client.c:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
//./tcp_client 192.168.x.x 8080
static void usage(const char* proc)
{
printf("usage:%s [server_ip] [server_port]\n",proc);
}
int main(int argc,char* argv[])
{
if(argc != 3) //命令行用法
{
usage(argv[0]);
return 1;
}
int sock=socket(AF_INET,SOCK_STREAM,0); //創建客戶端套接字
if(sock<0)
{
perror("socket");
return 2;
}
struct sockaddr_in server;
server.sin_family=AF_INET;
server.sin_port=htons(atoi(argv[2]));
server.sin_addr.s_addr=inet_addr(argv[1]);
if(connect(sock,(struct sockaddr*)&server,sizeof(server))<0) //客戶端與服務器建立連接
{
perror("connect");
return 1;
}
//連接成功
char buf[1024];
while(1)
{
//先往網絡上寫
printf("Please Enter# ");
fflush(stdout);
ssize_t s=read(0,buf,sizeof(buf)-1);
if(s>0)
{
buf[s-1]=0;
write(sock,buf,strlen(buf));
}
//從網絡上讀取服務器提供的信息
ssize_t _s=read(sock,buf,sizeof(buf)-1);
if(_s>0)
{
buf[_s]=0;
printf("server$ %s\n",buf);
}
}
//關閉sock,則斷開連接
close(sock);
return 0;
}