socket程式設計(TCP、UDP)
Linux下的Socket程式設計大體上包括Tcp Socket、Udp Socket即Raw Socket這三種,其中TCP和UDP方式的Socket程式設計用於編寫應用層的socket程式,是我們用得比較多的,而Raw Socket則用得相對較少,不在本文介紹範圍之列。
TCP Socket
基於TCP協議的客戶端/伺服器程式的一般流程一般如下:
它基本上可以分為三個部分:
一、建立連線:
- 伺服器呼叫socket()、bind()、listen()完成初始化後,呼叫accept()阻塞等待,處於監聽埠的狀態
-
客戶端呼叫socket()初始化後,呼叫connect()發出SYN段並阻塞等待伺服器應答
- 伺服器應答一個SYN-ACK段,客戶端收到後從connect()返回,同時應答一個ACK段,伺服器收到後從accept()返回。
二、傳輸資料:
建立連線後,TCP協議提供全雙工的通訊管道,伺服器端和客戶端根據協議可以通過read和write的反覆呼叫實現資料的傳輸
三、關閉連線:
當資料傳輸已經完成後,伺服器和客戶端可以呼叫Close關閉連線,一端關閉連線後,另一端read函式則會返回0,可以根據這個特徵來感應另一端的退出。
下面就以一個簡單的EchoServer演示一下如何建立伺服器端和客戶端程式碼,其中和socket相關api都會高亮顯示。
伺服器端示例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define MAXLINE 80
#define SERV_PORT 8000
int main(void)
{
char buf[MAXLINE];
int listenfd = 0;
listenfd =
sockaddr_in servaddr = {0};
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
bind(listenfd, (sockaddr *)&servaddr, sizeof(servaddr));
listen(listenfd, 20);
printf("Accepting connections ...\n");
while (1)
{
sockaddr_in cliaddr = {0};
socklen_t cliaddr_len = sizeof(cliaddr);
int connfd =
char str[INET_ADDRSTRLEN];
printf("connected from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port));
while(true)
{
int count = read(connfd, buf,
MAXLINE);
if (count == 0)
break;
write(connfd, buf, count);
}
close(connfd);
printf("closed from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port));
}
}
PS:這裡需要注意的一下的是sock函式的第二個引數SOCK_STREAM,它表示是一個TCP連線,後面我們會介紹通過傳入SOCK_DGRAM開啟udp連線。
伺服器端主體流程就是一個死迴圈,它接受一個socket連線,然後將其原封不動的返回給客戶端,待客戶端退出後,關閉socket連線,再次接受下一個socket連線。
客戶端程式碼如下:
#include <stdio.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define MAXLINE 80
#define SERV_PORT 8000
#define MESSAGE "hello world"
int main(int argc, char *argv[])
{
char buf[MAXLINE];
int sockfd = socket(AF_INET,
SOCK_STREAM, 0);
sockaddr_in servaddr = {0};
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
servaddr.sin_port = htons(SERV_PORT);
if (0 != connect(sockfd, (sockaddr *)&servaddr, sizeof(servaddr)))
{
printf("connected failed");
return 1;
}
write(sockfd, MESSAGE, sizeof(MESSAGE));
int count = read(sockfd, buf, MAXLINE);
printf("Response from server: %s\n",buf);
close(sockfd);
return 0;
}
客戶端程式碼比較簡單,這裡就不多介紹了。
UDP Socket
典型的UDP客戶端/伺服器通訊過程如下圖所示:
由於UDP不需要維護連線,程式邏輯簡單了很多,但是UDP協議是不可靠的,實際上有很多保證通訊可靠性的機制需要在應用層實現,可能反而會需要更多程式碼。
典型的示例如下:
/* server.cpp */
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define MAXLINE 80
#define SERV_PORT 8000
int main(void)
{
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
int sockfd = socket(AF_INET,
SOCK_DGRAM, 0);
sockaddr_in servaddr = {0};
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
bind(sockfd, (sockaddr *)&servaddr, sizeof(servaddr));
printf("Accepting connections ...\n");
while (1)
{
sockaddr_in cliaddr;
socklen_t cliaddr_len = sizeof(cliaddr);
int count = recvfrom(sockfd, buf, MAXLINE, 0, (sockaddr *)&cliaddr,
&cliaddr_len);
if (count < 0)
{
printf("recvfrom error");
continue;
}
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port));
sendto(sockfd, buf, count, 0, (sockaddr *)&cliaddr, sizeof(cliaddr));
}
}
/* client.cpp */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define MAXLINE 80
#define SERV_PORT 8000
int main(int argc, char *argv[])
{
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
int sockfd = socket(AF_INET,
SOCK_DGRAM, 0);
sockaddr_in servaddr = {0};
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
servaddr.sin_port = htons(SERV_PORT);
while (fgets(buf, MAXLINE, stdin) != NULL)
{
int count = sendto(sockfd, buf, strlen(buf), 0, (sockaddr *)&servaddr, sizeof(servaddr));
if (count == -1)
{
printf("sendto error");
return 0;
}
count = recvfrom(sockfd, buf, MAXLINE, 0, NULL, 0);
if (count == -1)
{
printf("recvfrom error");
return 0;
}
write(STDOUT_FILENO, buf, count);
}
close(sockfd);
return 0;
}