TCP客戶/服務器簡單Socket程序
建立一個 TCP 連接時會發生下述情形:
1. 服務器必須準備好接受外來的連接。這通常通過調用 socket、bind 和 listen 這三個函數來完成,我們稱之為被動打開。
2. 客戶通過調用 connect 發起主動打開,這導致客戶TCP發送一個SYN(同步)分節,標識希望連接的服務器端口以及初始序號。通常SYN分節不攜帶數據,其所在IP數據報只含有一個IP首部、一個TCP首部及可能有的TCP選項。
3. 服務器發送回一個包含服務器初始序號以及對客戶端 SYN 段確認的 SYN + ACK 段作為應答,由於一個 SYN 占用一個序號,因此確認序號設置為客戶端初始序號加 1。
4. 客戶端發送確認序號為服務器初始序號加 1 的 ACK 段,對服務器 SYN 段進行確認。
這種交換至少需要三個分組,因此稱之為TCP的三路握手。
一旦TCP建立連接,客戶/服務器之間便可以進行數據通信。
1. 服務器端
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<unistd.h>
#include<netinet/in.h>
#include <errno.h>
#define PORT 6666
int main(int argc,char **argv)
{
int ser_sockfd,cli_sockfd;
int err,n;
int addlen;
struct sockaddr_in ser_addr;
struct sockaddr_in cli_addr;
char recvline[200],sendline[200];
ser_sockfd = socket(AF_INET,SOCK_STREAM,0); //創建套接字
if(ser_sockfd == -1)
{
printf("socket error:%s\n",strerror(errno));
return -1;
}
bzero(&ser_addr,sizeof(ser_addr));
/*在待捆綁到該TCP套接口(sockfd)的網際套接口地址結構中填入通配地址(INADDR_ANY)
和服務器的眾所周知端口(PORT,這裏為6666),這裏捆綁通配地址是在告知系統:要是系統是
多宿主機(具有多個網絡連接的主機),我們將接受宿地址為任何本地接口的地址*/
ser_addr.sin_family = AF_INET;
ser_addr.sin_addr.s_addr = htonl(INADDR_ANY);
ser_addr.sin_port = htons(PORT);
//將網際套接口地址結構捆綁到該套接口
err = bind(ser_sockfd,(struct sockaddr *)&ser_addr,sizeof(ser_addr));
if(err == -1)
{
printf("bind error:%s\n",strerror(errno));
return -1;
}
//將套接口轉換為一個監聽套接口,監聽等待來自客戶端的連接請求
err = listen(ser_sockfd,5);
if(err == -1)
{
printf("listen error\n");
return -1;
}
printf("listen the port:\n");
while(1)
{
addlen = sizeof(struct sockaddr);
//等待阻塞,等待客戶端申請,並接受客戶端的連接請求
//accept成功,將創建一個新的套接字,並為這個新的套接字分配一個套接字描述符
cli_sockfd = accept(ser_sockfd,(struct sockaddr *)&cli_addr,&addlen);
if(cli_sockfd == -1)
{
printf("accept error\n");
}
//數據傳輸
while(1)
{
printf("waiting for client...\n");
n = recv(cli_sockfd,recvline,1024,0);
if(n == -1)
{
printf("recv error\n");
}
recvline[n] = ‘\0‘;
printf("recv data is:%s\n",recvline);
printf("Input your words:");
scanf("%s",sendline);
send(cli_sockfd,sendline,strlen(sendline),0);
}
close(cli_sockfd);
}
close(ser_sockfd);
return 0;
}
1.首先通過 socket 函數創建套接字,此時套接字數據結構字段並未填充,在使用之前必須調用過程來填充對應字段,這裏在地址結構中填入通配地址(INADDR_ANY),通配地址就是指定地址為 0.0.0.0 的地址,表示服務器接受機器上所有IP地址的連接,用於多IP機器上。這樣無論客戶 connect 哪個IP地址,服務器端都會接收到請求,即接受宿地址為任何本地接口的地址。如果是指定地址,那麽機器只有 connect 這個地址才能成功。後面是填充端口號,如果指定為 0,則由系統隨機選擇一個未被使用的端口。
2. bind 將沒有指定端口的 socket(ser_sockfd)綁定到我們指定的端口上(通配地址+指定端口號),服務器是通過它們的眾所周知端口被大家認識的。這樣 socket(ser_sockfd)就與指定的端口產生了關聯,即指向了指定端口。
3. listen 將套接口轉換為一個監聽套接口,被動打開,允許監聽客戶端的連接請求,然後 accept 客戶端的連接請求,沒有請求則阻塞。
5. accept 成功後,將創建新的套接字,並為新套接字分配一個套接口描述符,該套接字除了記錄本地(服務器)的IP和端口號信息外,還記錄了目的(客戶)IP和端口號信息。服務器與客戶的通信則是通過該新創建的套接字(已連接套接字)進行。
2. 客戶端
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<unistd.h>
#include<netinet/in.h>
#define PORT 6666
int main(int argc,char **argv)
{
int sockfd;
int err,n;
struct sockaddr_in addr_ser;
char sendline[200],recvline[200];
sockfd = socket(AF_INET,SOCK_STREAM,0); //創建套接字
if(sockfd == -1)
{
printf("socket error\n");
return -1;
}
bzero(&addr_ser,sizeof(addr_ser));
/*用通配地址和指定端口號裝填一個網際接口地址結構*/
addr_ser.sin_family = AF_INET;
addr_ser.sin_addr.s_addr = htonl(INADDR_ANY);
addr_ser.sin_port = htons(PORT);
//TCP:客戶(sockfd)向服務器(套接口地址結構)發起連接,主動請求
//服務器的IP地址和端口號有參數addr_ser指定
err = connect(sockfd,(struct sockaddr *)&addr_ser,sizeof(addr_ser));
if(err == -1)
{
printf("connect error\n");
return -1;
}
printf("connect with server...\n");
//數據傳輸
while(1)
{
printf("Input your words:");
scanf("%s",sendline);
send(sockfd,sendline,strlen(sendline),0);
printf("waiting for server...\n");
n = recv(sockfd,recvline,100,0);
recvline[n] = ‘\0‘;
printf("recv data is:%s\n",recvline);
}
return 0;
}
1. 客戶端同樣通過 socket 創建套接字,TCP 客戶通常不把IP地址捆綁到它的套接口上,當連接套接口時,內核將根據所用外出網絡接口來確定源IP地址,並選擇一個臨時端口作為源端口。
2. 用通配地址和指定端口填充的是待連接服務器端的套接字地址結購,這裏采用的是通配地址,由於服務器端指定的是通配地址,即接受機器上所有IP地址的連接,同樣客戶也可向機器上任何IP地址發起連接,服務器端都會接收到。
3. 客戶端向服務器發起連接請求,connect 成功後,其請求連接的服務器的IP和端口號信息將會寫入該套接字,這樣該套接字也同時記錄了本地和目的的IP地址和端口信息。也就可以進行通信了。
結果如下:
TCP客戶/服務器簡單Socket程序