第13章 TCP/IP和網(wǎng)絡(luò)編程
TCP/IP協(xié)議
TCP/IP協(xié)議是利用 IP 進行通信時所必須用到的協(xié)議群的統(tǒng)稱。具體來說,IP 或 ICMP、TCP 或 UDP、TELNET 或 FTP、以及 HTTP 等都屬于 TCP/IP 協(xié)議。他們與 TCP 或 IP 的關(guān)系緊密,是互聯(lián)網(wǎng)必不可少的組成部分。TCP/IP 一詞泛指這些協(xié)議,因此,有時也稱 TCP/IP 為網(wǎng)際協(xié)議群。
互聯(lián)網(wǎng)進行通信時,需要相應(yīng)的網(wǎng)絡(luò)協(xié)議,TCP/IP 原本就是為使用互聯(lián)網(wǎng)而開發(fā)制定的協(xié)議族。因此,互聯(lián)網(wǎng)的協(xié)議就是 TCP/IP,TCP/IP 就是互聯(lián)網(wǎng)的協(xié)議。
IP主機和IP地址
主機是支持TCP/IP協(xié)議的計算機或設(shè)備。每個主機由一個32位的IP地址來標(biāo)識。為了方便起見32位的IP地址號通常用點記法表示,例如:134.121.64.1,其中各個字節(jié)用點號分開。主機也可以用主機名來表示,如dnsleecwsuedu。實際上,應(yīng)用程序通常使用主機名而不是IP地址。在這個意義上說,主機名就等同于IP地址,因為給定其中一個,我們可以通過DNS(域名系統(tǒng))(RFC1341987RFC10351987)服務(wù)器找到另一個,它將IP地址轉(zhuǎn)換為主機名,反之亦然。
IP地址分為兩部分,即NetworkID字段和HostID字段。發(fā)往IP地址的數(shù)據(jù)包首先被發(fā)送到具有相同networkID的路由器。路由器將通過HostID將數(shù)據(jù)包轉(zhuǎn)發(fā)到網(wǎng)絡(luò)中的特定主機。每個主機都有一個本地主機名。localhost默認IP地址為127001。本地主機的鏈路層是一個回送虛擬設(shè)備,它將每個數(shù)據(jù)包路由回同一個localhost。這個特性可以讓我們在同一臺計算機上運行TCP/IP應(yīng)用程序而不需要實際連接到互聯(lián)網(wǎng)。
IP協(xié)議
IP協(xié)議用于在IP主機之間發(fā)送/接收數(shù)據(jù)包。IP盡最大努力運行。IP主機發(fā)送數(shù)據(jù)包,但它不能保證數(shù)據(jù)包會被發(fā)送到它們的目的地,也不能保證按順序發(fā)送。 這意味著IP并非可靠的協(xié)議。必要時,必須在IP層的上面實現(xiàn)可靠性。
IP數(shù)據(jù)包格式
IP數(shù)據(jù)包由IP頭、發(fā)送方IP地址、接收方IP地址以及數(shù)據(jù)組成。每個IP數(shù)據(jù)包大小不超過64KB,IP頭包含了有關(guān)數(shù)據(jù)包的信息,如數(shù)據(jù)包的總長度、使用協(xié)議等,一個IP包頭的格式基本如下:

- UDP
UDP(用戶數(shù)據(jù)報協(xié)議)在IP上運行,用于發(fā)送/接收數(shù)據(jù)報。與IP類似,UDP不能保證可靠性,但是快速高效。 - TCP
TCP(傳輸控制協(xié)議)是一種面向連接的協(xié)議,用于發(fā)送/接收數(shù)據(jù)流。TCP也可在IP 上運行,但它保證了可靠的數(shù)據(jù)傳輸。通常,UDP類似于發(fā)送郵件的USPS,而TCP類似于電話連接。 - 端口編號
在各主機上,多個應(yīng)用程序可同時使用TCP/UDP。每個應(yīng)用程序由三個組成部分唯一標(biāo)識:應(yīng)用程序 = (主機IP,協(xié)議,端口號)
下圖給出了在傳輸層中使用TCP的一些應(yīng)用程序及其默認端口號
![]()
網(wǎng)絡(luò)和主機字節(jié)序
計算機可以使用大端字節(jié)序,也可以使用小端字節(jié)序。在互聯(lián)網(wǎng)上,數(shù)據(jù)始終按照網(wǎng)絡(luò)序排序,這是大端。
TCP/IP網(wǎng)絡(luò)中的數(shù)據(jù)流
TCP/IP網(wǎng)絡(luò)中各層數(shù)據(jù)格式及數(shù)據(jù)流路徑如下:

- 網(wǎng)絡(luò)編程
大多數(shù)的網(wǎng)絡(luò)編程任務(wù)都是基于服務(wù)器-客戶端的模型的。在這個模型中,客戶端首先從服務(wù)器主機上運行服務(wù)進程。然后在客戶端主機上運行客戶端。 - 服務(wù)器-客戶端模型:
在UDP中,服務(wù)器等待來自客戶端的數(shù)據(jù)報,處理數(shù)據(jù)報并生成對客戶機的響應(yīng)。在TCP中,服務(wù)器先建立一個于=與客戶端的虛擬電路(虛電路),再在服務(wù)器和客戶端進行數(shù)據(jù)報傳輸。 - 套接字編程
在網(wǎng)絡(luò)編程中,TCP/IP的用戶界面是通過一系列C語言庫函數(shù)和系統(tǒng)調(diào)用來實現(xiàn)的,這些函數(shù)和系統(tǒng)調(diào)用統(tǒng)稱為套接字API。為了使用套接字API,我們需要套接字地址結(jié)構(gòu),它用于標(biāo)識服務(wù)器和客戶機。netdb.h和sys/socket.h中有套接字地址結(jié)構(gòu)的定義。 - 套接字地址
struct socketddr_in {
sa_family_t sin_family; //TCP/IP中的sin_family始終是AF_INET
in_port_t sin_port; //按網(wǎng)絡(luò)字節(jié)順序排列的端口號
struct in_addr sin_addr; //按網(wǎng)絡(luò)字節(jié)順序排列的主機IP地址
};
struct in_addr {
uint32_t s_addr; //按網(wǎng)絡(luò)字節(jié)順序排列的主機IP地址
}
- 套接字API
服務(wù)器需要創(chuàng)建一個套接字,并將其與包含服務(wù)器IP地址和端口號的套接字地址綁定。它(指服務(wù)器套接字)可以使用一個固定端口號,或是操作系統(tǒng)內(nèi)核所選擇的端口號(當(dāng)sin_port為0時)。為了與服務(wù)器通信,客戶端也需要一個套接字。UDP套接字可以直接綁定到服務(wù)器地址,如果一個客戶端套接字沒有綁定到任何特定服務(wù)器,它就必須在后續(xù)的sendto()/recvfrom()調(diào)用中提供一個包含服務(wù)器IP和端口號的套接字地址。 - socket系統(tǒng)調(diào)用
1.int套接字
格式:int (int 域,int 類型,int 協(xié)議)
2.int bind()
格式:int bind(int sockfd , struct sockaddr *addr , socklen_t addrlen);
3.UDP套接字
格式:
ssize_t sendto(int sockfd , const void *buf , size_t len , int flags ,const
struct sockaddr *dest_addr , socklen_t addrlen);
ssize_t recvfrom(int sockfd , void *buf , size_t len , itn flags ,
struct sockaddr *src_addr , socklen_t *addr);
4.TCP套接字
在創(chuàng)建套接字并將其綁定到服務(wù)器地址之后,TCP服務(wù)器使用listen()和acccpt()來接 收來自客戶機的連接
int Iistcn(int sockfd, int backlog);
listen()將sockfd引用的套接字標(biāo)記為將用于接收連入連接的套接字。backlog參數(shù)定義了等 待連接的最大隊列長度。
int accept(int sockfd, struct sockaddr *addr, sockien_t *addrlen);
accept()系統(tǒng)調(diào)用與基于連接的套接字一起使用。它提取等待連接隊列上的第一個連接請求 用于監(jiān)聽套接字sockfd,創(chuàng)建一個新的連接套接字,并返回一個引用該套接字的新文件描 述符,與客戶機主機連接。在執(zhí)行accept()系統(tǒng)調(diào)用時,TCP服務(wù)器阻塞,直到客戶機通過 coimectO建立連接。
int connect(int sockfd, const struct sockaddr *addr, socklen t addrlen);
connect()系統(tǒng)調(diào)用將文件描述符sockfd引用的套接字連接到addr指定的地址,addrlen參數(shù) 指定addr的大小。addr中的地址格式由套接字sockfd的地址空間決定。
5.send()/read()以及recv/write()
實踐

- 服務(wù)器.c
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include "aes_options.h" //add
int main()
{
int server_fd;
int client_fd;
int len;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
int sin_size;
char buffer[BUFSIZ];
// printf("%d",BUFSIZ);
memset(&server_addr, 0, sizeof(server_addr)); // initialize struct
memset(&server_addr, 0, sizeof(client_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(9000);
if ((server_fd = socket(PF_INET, SOCK_STREAM, 0)) < 0) // create server socket
{
perror("socket create failed");
return 1;
}
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) < 0) // bind info on server socket
{
perror("bind failed");
return 1;
}
listen(server_fd, 5); // listen port 9000
sin_size = sizeof(struct sockaddr_in);
if ((client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &sin_size)) < 0)
{
perror("accept failed");
return 1;
}
printf("accept client %s\n", inet_ntoa(client_addr.sin_addr));
len = send(client_fd, "Welcome to my server\n", 21, 0);
while ((len = recv(client_fd, buffer, BUFSIZ, 0)) > 0)
{
char *decryto_string = NULL; // add
decrypt(buffer, &decryto_string, len); // add
printf("%s \n", decryto_string);
if (send(client_fd, decryto_string, len, 0) < 0) // modified
{
perror("send failed");
return 1;
}
}
close(client_fd);
close(server_fd);
return 0;
}
客戶端.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include "aes_options.h" //add
int main()
{
int len;
int client_sockfd;
struct sockaddr_in server_addr;
char buffer[BUFSIZ];
char *encrypt_string = NULL;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
server_addr.sin_port = htons(9000);
if((client_sockfd = socket(PF_INET, SOCK_STREAM, 0)) < 0)
{
perror("socket create failed");
return 1;
}
int username;
int password;
printf("enter the username:");
scanf("%d", &username);
printf("enter the password:");
scanf("%d", &password);
if (username !=2020 || password != 1212)
{
printf("uncorrect name!\n");
return 1;
}
if(connect(client_sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) < 0)
{
perror("connect failed");
return 1;
}
printf("connect to server\n");
len = recv(client_sockfd, buffer, BUFSIZ, 0);
buffer[len] = '\0';
printf("%s", buffer);
while(1)
{
printf("enter a data:");
scanf("%s", buffer);
if(!strcmp(buffer,"quit"))
break;
int encrypt_length = encrypt(buffer, &encrypt_string); //add
len = send(client_sockfd, encrypt_string, encrypt_length, 0); //add
len = recv(client_sockfd, buffer, BUFSIZ, 0);
buffer[len] = '\0';
printf("recived:%s \n", buffer);
}
close(client_sockfd);
printf("bye");
return 0;
}
/*char username;
int password;
printf("enter the username:");
scanf("%s", &username);
printf("enter the password:");
scanf("%d", &password);
if (username != "ycy" || password != 20201212)
{
printf("uncorrect name!\n");
return 1;
}
*/
實驗中遇到的問題

蘇格拉底挑戰(zhàn)





浙公網(wǎng)安備 33010602011771號