嵌入式 Linux網絡編程(二)――TCP編程模型
一、TCP編程模型
TCP編程的一般模型如下圖:
TCP編程模型分為客戶端和服務器端編程,兩者編程流程如下:
TCP服務器端編程流程:
A、創建套接字;
B、綁定套接字;
C、設置套接字為監聽模式,進入被動接受連接狀態;
D、接受請求,建立連接;
E、讀寫數據;
F、終止連接。
TCP客戶端編程流程:
A、創建套接字;
B、與遠程服務器建立連接;
C、讀寫數據;
D、終止連接。
二、TCP迭代服務器編程模型
TCP循環服務器接受一個客戶端的連接,然後處理,完成了客戶端的所有請求後,斷開連接。TCP循環服務器一次只能處理一個客戶端的請求,只有在完成這個客戶的所有請求病斷開這個客戶端後,服務器才可以繼續後面的請求。如果有一個客戶端占住服務器不放時,其它的客戶端都不能工作了。
TCP循環服務器模型為:
socket(...);
bind(...);
listen(...);
while(1)
{
accept(...);
process(...);
close(...);
}
代碼實例:
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>
#define PORT 8888
#define LISTEN_QUEUE 10
#define BUFFER_SIZE 1024
int main()
{
///定義sockfd
int listenfd = socket(AF_INET,SOCK_STREAM, 0);
///定義sockaddr_in
struct sockaddr_in server_sockaddr;
bzero(&server_sockaddr, sizeof(server_sockaddr));
server_sockaddr.sin_family = AF_INET;
server_sockaddr.sin_port = htons(PORT);
server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
///bind,成功返回0,出錯返回-1
if(bind(listenfd, (struct sockaddr *)&server_sockaddr, sizeof(server_sockaddr))==-1)
{
fprintf(stderr, "bind function failed.\n");
exit(-1);
}
///listen,成功返回0,出錯返回-1
if(listen(listenfd,LISTEN_QUEUE) == -1)
{
fprintf(stderr, "listen function failed.\n");
exit(-1);
}
fprintf(stdout, "listening on %d\n", PORT);
///客戶端套接字
char recvbuf[BUFFER_SIZE];
char sendbuf[BUFFER_SIZE];
struct sockaddr_in client_addr;
socklen_t length = sizeof(client_addr);
bzero(&client_addr, sizeof(client_addr));
while(1)
{
bzero(recvbuf, sizeof(recvbuf));
bzero(sendbuf, sizeof(sendbuf));
///成功返回非負描述字,出錯返回-1
int connsockfd = accept(listenfd, (struct sockaddr*)&client_addr, &length);
if(connsockfd<0)
{
fprintf(stderr, "connect function failed.\n");
exit(-1);
}
int len = recvfrom(connsockfd, recvbuf, sizeof(recvbuf), 0, (struct sockaddr *)&client_addr, &len);
if(strcmp(recvbuf,"exit\n")==0)
break;
if(strcmp(recvbuf,"q\n")==0)
break;
if(strcmp(recvbuf,"quit\n")==0)
break;
fprintf(stdout, "have a new client:%s\n", inet_ntoa(client_addr.sin_addr));
fprintf(stdout, "message: %s\n", recvbuf);
strcpy(sendbuf, recvbuf);
send(connsockfd, sendbuf, len, 0);
close(connsockfd);
}
close(listenfd);
return 0;
}
三、TCP並發服務器編程模型
1、TCP多進程並發服務器
TCP多進程並發服務器的思想是每一個客戶機的請求並不由服務器直接處理,而是由服務器創建一個子進程來處理。
TCP多進程並發服務器:
socket(...);
bind(...);
listen(...);
while(1)
{
accpet(...);
if(fork(...) == 0)
{
process(...);
close(...);
exit(...);
}
close(...);
}
代碼:
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>
#define PORT 8888
#define LISTEN_QUEUE 10
#define BUFFER_SIZE 1024
int main()
{
///定義sockfd
int listenfd = socket(AF_INET,SOCK_STREAM, 0);
///定義sockaddr_in
struct sockaddr_in server_sockaddr;
bzero(&server_sockaddr, sizeof(server_sockaddr));
server_sockaddr.sin_family = AF_INET;
server_sockaddr.sin_port = htons(PORT);
server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
///bind,成功返回0,出錯返回-1
if(bind(listenfd, (struct sockaddr *)&server_sockaddr, sizeof(server_sockaddr))==-1)
{
fprintf(stderr, "bind function failed.\n");
exit(-1);
}
///listen,成功返回0,出錯返回-1
if(listen(listenfd,LISTEN_QUEUE) == -1)
{
fprintf(stderr, "listen function failed.\n");
exit(-1);
}
fprintf(stdout, "listening on %d\n", PORT);
///客戶端套接字
struct sockaddr_in client_addr;
socklen_t length = sizeof(client_addr);
bzero(&client_addr, sizeof(client_addr));
///成功返回非負描述字,出錯返回-1
while(1)
{
int connsockfd = accept(listenfd, (struct sockaddr*)&client_addr, &length);
if(connsockfd<0)
{
fprintf(stderr, "connect function failed.\n");
exit(-1);
}
pid_t pid = fork();
if(pid == 0)
{
close(listenfd);//關閉從父進程繼承的監聽套接字
char recvbuf[BUFFER_SIZE];
char sendbuf[BUFFER_SIZE];
bzero(recvbuf, sizeof(recvbuf));
bzero(sendbuf, sizeof(sendbuf));
int len;
while((len = recv(connsockfd, recvbuf, sizeof(recvbuf), 0)) > 0)
{
if(strcmp(recvbuf,"exit\n")==0 || strcmp(recvbuf,"q\n")==0 || strcmp(recvbuf,"quit\n")==0)
break;
else
{
fprintf(stdout, "have a new client:%s port: %d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
fprintf(stdout, "message: %s\n", recvbuf);
strcpy(sendbuf, recvbuf);
send(connsockfd, sendbuf, len, 0);
}
}
close(connsockfd);
fprintf(stdout, "client %s close.\n", inet_ntoa(client_addr.sin_addr));
exit(0);
}
else if(pid > 0)
{
close(connsockfd);
}
else
{
fprintf(stderr, "fork function failed.\n");
exit(-1);
}
}
close(listenfd);
return 0;
}
2、TCP多線程並發服務器
多線程服務器是對多進程的服務器的改進 ,由於多進程服務器在創建進程時要消耗較大的系統資源 ,所以用線程來取代進程 ,這樣服務處理程序可以較快的創建。據統計 ,創建線程與創建進程要快10――100倍,所以又把線程稱為“輕量級”進程。線程與進程不同的是:一個進程內的所有線程共享相同的全局內存、全局變量等信息。多線程需要解決線程的同步問題。
TCP多線程服務器模板:
socket(...);
bind(...);
listen(...);
while(1)
{
accpet(...);
if((pthread_create(...))!==-1)
{
process(...);
close(...);
exit(...);
}
close(...);
}
代碼:
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>
#include <pthread.h>
#define PORT 8888
#define LISTEN_QUEUE 10
#define BUFFER_SIZE 1024
void *client_process(void *arg)
{
int recvlen = 0;
char recvbuf[BUFFER_SIZE];
bzero(recvbuf, sizeof(recvbuf));
char sendbuf[BUFFER_SIZE];
bzero(sendbuf, sizeof(sendbuf));
int connfd = (int )arg; // 已連接套接字
// 接收數據
while((recvlen = recv(connfd, recvbuf, sizeof(recvbuf), 0)) > 0)
{
if(strcmp(recvbuf,"exit\n")==0 || strcmp(recvbuf,"q\n")==0 || strcmp(recvbuf,"quit\n")==0)
break;
else
{
fprintf(stdout, "message: %s\n", recvbuf);
strcpy(sendbuf, recvbuf);
send(connfd, sendbuf, sizeof(sendbuf), 0);
}
}
printf("client closed!\n");
close(connfd); //關閉已連接套接字
return NULL;
}
int main()
{
///定義sockfd
int listenfd = socket(AF_INET,SOCK_STREAM, 0);
///定義sockaddr_in
struct sockaddr_in server_sockaddr;
bzero(&server_sockaddr, sizeof(server_sockaddr));
server_sockaddr.sin_family = AF_INET;
server_sockaddr.sin_port = htons(PORT);
server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
///bind,成功返回0,出錯返回-1
if(bind(listenfd, (struct sockaddr *)&server_sockaddr, sizeof(server_sockaddr))==-1)
{
fprintf(stderr, "bind function failed.\n");
exit(-1);
}
///listen,成功返回0,出錯返回-1
if(listen(listenfd,LISTEN_QUEUE) == -1)
{
fprintf(stderr, "listen function failed.\n");
exit(-1);
}
fprintf(stdout, "listening on %d\n", PORT);
///客戶端套接字
struct sockaddr_in client_addr;
socklen_t length = sizeof(client_addr);
bzero(&client_addr, sizeof(client_addr));
///成功返回非負描述字,出錯返回-1
pthread_t thread_id;
while(1)
{
int connsockfd = accept(listenfd, (struct sockaddr*)&client_addr, &length);
if(connsockfd<0)
{
fprintf(stderr, "connect function failed.\n");
exit(-1);
}
fprintf(stdout, "have a new client:%s port: %d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
pthread_create(&thread_id, NULL, (void *)client_process, (void *)connsockfd); //創建線程
pthread_detach(thread_id); // 線程分離,結束時自動回收資源
}
close(listenfd);
return 0;
}
3、TCP IO復用服務器模型
I/O復用技術是為了解決進程或線程阻塞到某個 I/ O系統調用而出現的技術 ,使進程不阻塞於某個特定的I/ O系統調用,也可用於並發服務器的設計,常用函數select 或 poll來實現。
socket(...); // 創建套接字
bind(...); // 綁定
listen(...); // 監聽
while(1)
{
if(select(...) > 0) // 檢測監聽套接字是否可讀
{
if(FD_ISSET(...)>0) // 套接字可讀,證明有新客戶端連接服務器
{
accpet(...);// 取出已經完成的連接
process(...);// 處理請求,反饋結果
}
}
close(...); // 關閉連接套接字:accept()返回的套接字
}
關於IO復用將在後續詳細介紹
4、TCP客戶端編程模型
socket(...);
connect(...);
process(...);
close(...);
代碼:
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>
#define PORT 8888
#define BUFFER_SIZE 1024
int main()
{
///定義sockfd
int clientsockfd = socket(AF_INET, SOCK_STREAM, 0);
///定義sockaddr_in
struct sockaddr_in servaddr;
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(PORT); ///服務器端口
servaddr.sin_addr.s_addr = inet_addr("192.168.0.200"); ///服務器ip
///連接服務器,成功返回0,錯誤返回-1
if (connect(clientsockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
{
fprintf(stderr, "connect function failed.\n");
exit(-1);
}
char sendbuf[BUFFER_SIZE];
char recvbuf[BUFFER_SIZE];
bzero(sendbuf, sizeof(sendbuf));
bzero(recvbuf, sizeof(recvbuf));
while (1)
{
fgets(sendbuf, sizeof(sendbuf), stdin);
send(clientsockfd, sendbuf, strlen(sendbuf),0); ///發送
if(strcmp(sendbuf,"exit\n")==0)
break;
if(strcmp(sendbuf,"q\n")==0)
break;
if(strcmp(sendbuf,"quit\n")==0)
break;
recv(clientsockfd, recvbuf, sizeof(recvbuf),0); ///接收
fprintf(stdout, "%s\n", recvbuf);
bzero(sendbuf, sizeof(sendbuf));
bzero(recvbuf, sizeof(recvbuf));
}
close(clientsockfd);
return 0;
}
本文出自 “生命不息,奮斗不止” 博客,轉載請與作者聯系!