TCP伺服器端和客戶端程式設計
1、socket函式:為了執行網路輸入輸出,一個程序必須做的第一件事就是呼叫socket函式獲得一個檔案描述符。
----------------------------------------------------------------- |
第一個引數指明瞭協議簇,目前支援5種協議簇,最常用的有AF_INET(IPv4協議)和AF_INET6(IPv6協議);第二個引數指明套介面型別,有三種類型可選:SOCK_STREAM(位元組流套介面)、SOCK_DGRAM(資料報套介面)和SOCK_RAW(原始套介面);如果套介面型別不是原始套介面,那麼第三個引數就為0。
2、connect函式:當用socket建立了套介面後,可以呼叫connect為這個套接字指明遠端端的地址;如果是位元組流套介面,connect就使用三次握手建立一個連線;如果是資料報套介面,connect僅指明遠端端地址,而不向它傳送任何資料。
----------------------------------------------------------------- |
第一個引數是socket函式返回的套介面描述字;第二和第三個引數分別是一個指向套介面地址結構的指標和該結構的大小。
這些地址結構的名字均已“sockaddr_”開頭,並以對應每個協議族的唯一字尾結束。以IPv4套介面地址結構為例,它以“sockaddr_in”命名,定義在標頭檔案<netinet/in.h>;以下是結構體的內容:
------------------------------------------------------------------ }; |
3、bind函式:為套介面分配一個本地IP和協議埠,對於網際協議,協議地址是32位IPv4地址或128位IPv6地址與16位的TCP或UDP埠號的組合;如指定埠為0,呼叫bind時核心將選擇一個臨時埠,如果指定一個通配IP地址,則要等到建立連線後核心才選擇一個本地IP地址。
------------------------------------------------------------------- |
第一個引數是socket函式返回的套介面描述字;第二和第第三個引數分別是一個指向特定於協議的地址結構的指標和該地址結構的長度。
4、listen函式:listen函式僅被TCP伺服器呼叫,它的作用是將用sock建立的主動套介面轉換成被動套介面,並等待來自客戶端的連線請求。
------------------------------------------------------------------- |
第一個引數是socket函式返回的套介面描述字;第二個引數規定了核心為此套介面排隊的最大連線個數。由於listen函式第二個引數的原因,核心要維護兩個佇列:以完成連線佇列和未完成連線佇列。未完成佇列中存放的是TCP連線的三路握手為完成的連線,accept函式是從以連線佇列中取連線返回給程序;當以連線佇列為空時,程序將進入睡眠狀態。
5、accept函式:accept函式由TCP伺服器呼叫,從已完成連線佇列頭返回一個已完成連線,如果完成連線佇列為空,則程序進入睡眠狀態。
------------------------------------------------------------------- |
第一個引數是socket函式返回的套介面描述字;第二個和第三個引數分別是一個指向連線方的套介面地址結構和該地址結構的長度;該函式返回的是一個全新的套介面描述字;如果對客戶段的資訊不感興趣,可以將第二和第三個引數置為空。
6、write和read函式:當伺服器和客戶端的連線建立起來後,就可以進行資料傳輸了,伺服器和客戶端用各自的套接字描述符進行讀/寫操作。因為套接字描述符也是一種檔案描述符,所以可以用檔案讀/寫函式write()和read()進行接收和傳送操作。
(1)write()函式用於資料的傳送。
-------------------------------------------------------------------
#include <unistd.h>
int write(int sockfd, char *buf, int len);
回:非負---成功 -1---失敗
-------------------------------------------------------------------
引數sockfd是套接字描述符,對於伺服器是accept()函式返回的已連線套接字描述符,對於客戶端是呼叫socket()函式返回的套接字描述符;引數buf是指向一個用於傳送資訊的資料緩衝區;len指明傳送資料緩衝區的大小。
(2)read()函式用於資料的接收。
-------------------------------------------------------------------
#include <unistd.h>
int read(int sockfd, char *buf, intlen);
回:非負---成功 -1---失敗
-------------------------------------------------------------------
引數sockfd是套接字描述符,對於伺服器是accept()函式返回的已連線套接字描述符,對於客戶端是呼叫socket()函式返回的套接字描述符;引數buf是指向一個用於接收資訊的資料緩衝區;len指明接收資料緩衝區的大小。
7、send和recv函式:TCP套接字提供了send()和recv()函式,用來發送和接收操作。這兩個函式與write()和read()函式很相似,只是多了一個附加的引數。
(1)send()函式用於資料的傳送。
-------------------------------------------------------------------
#include <sys/types.h>
#include < sys/socket.h >
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
回:返回寫出的位元組數---成功 -1---失敗
-------------------------------------------------------------------
前3個引數與write()相同,引數flags是傳輸控制標誌。
(2)recv()函式用於資料的傳送。
-------------------------------------------------------------------
#include <sys/types.h>
#include < sys/socket.h >
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
回:返回讀入的位元組數---成功 -1---失敗
-------------------------------------------------------------------
前3個引數與read()相同,引數flags是傳輸控制標誌。
五、實驗步驟
1、登陸進入ubuntu作業系統,新建一個檔案,命名為tcpserver.c(為了方便起見,可以進入“home”,再進入使用者目錄,在使用者目錄下新建tcpserver.c)。
2、在tcpserver.c中編寫伺服器端程式程式碼並儲存。
3、在“終端”(“Applications”→“附件”→“終端”)中執行命令進入tcpserver.c所在目錄。(pwd命令可以顯示當前所在目錄;ls命令可以顯示當前目錄下的檔案和資料夾資訊;cd..命令可以進入上一級目錄;cd 目錄名 命令可以進入當前所示的某個目錄。)
4、執行命令gcc –o tcpserver tcpserver.c生成可執行檔案tcpserver。
5、執行命令./ tcpserver,觀察結果。
6、認真分析原始碼,體會如何編寫一個TCP伺服器端程式。
六、參考程式(tcpserver.c)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 1234
#define BACKLOG 1
int main()
{
int listenfd, connectfd;
struct sockaddr_in server;
struct sockaddr_in client;
socklen_t addrlen;
if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("Creating socket failed.");
exit(1);
}
int opt =SO_REUSEADDR;
setsockopt(listenfd,SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
bzero(&server,sizeof(server));
server.sin_family=AF_INET;
server.sin_port=htons(PORT);
server.sin_addr.s_addr= htonl (INADDR_ANY);
if(bind(listenfd, (struct sockaddr *)&server, sizeof(server)) == -1) {
perror("Binderror.");
exit(1);
}
if(listen(listenfd,BACKLOG)== -1){ /* calls listen() */
perror("listen()error\n");
exit(1);
}
addrlen =sizeof(client);
if((connectfd = accept(listenfd,(struct sockaddr*)&client,&addrlen))==-1) {
perror("accept()error\n");
exit(1);
}
printf("Yougot a connection from cient's ip is %s, prot is %d\n",inet_ntoa(client.sin_addr),htons(client.sin_port));
send(connectfd,"Welcometo my server.\n",22,0);
close(connectfd);
close(listenfd);
return 0;
}
實驗二 TCP客戶端程式設計