0%

套接字编程,简单程序的编写

Socket 编程

2 种传输层服务的socket类型:

  • TCP: 可靠的、字节流的服务
  • UDP: 不可靠(数据 UDP 数据报)服务

UDP 与 TCP

UDP:在客户端和服务器之间没有连接

  • 没有握手
  • 发送端在每一个报文中明确地指定目标的 IP 地址和端口号
  • 服务器必须从收到的分组中提取出发送端的 IP 地址和端口号

UDP:传送的数据可能乱序,也可能丢失

进程视角看 UDP 服务:
UDP 为客户端和服务器提供不可靠的字节组的传送服务

TCP:面向连接的协议,在客户和服务器能够开始互相发送数据之前,它们先要握手和创建一个 TCP 连接

服务器首先运行,等待连接建立

1:服务器进程必须先处于运行状态

  • 创建欢迎 socket
  • 和本地端口捆绑
  • 在欢迎 socket 上阻塞式等待接收
    用户的连接

客户端主动和服务器建立连接:

2:创建客户端本地套接字(隐式捆绑到本地 port)

  • 指定服务器进程的IP地址和端口号,与服务器进程连接

3 :当与客户端连接请求到来时

  • 服务器接受来自用户端的请求,解除阻塞式等待,返回一个新的 socket(与欢迎 socket 不一样),与客户端通信
    • 允许服务器与多个客户端通信
    • 使用源 IP 和源端

4:连接 API 调用有效时,客户端 P 与服务器建立了 TCP 连接

从应用程序的角度:
TCP在客户端和服务器进程之间提供了可靠的、字节流(管道)服务

一个简单的客户-服务器应用程序

  • 客户从其键盘读取一行字符(数据)并将该数据向服务器发送;
  • 服务器接收该数据并将这些字符转换为大写;
  • 服务器将修改的数据发送给客户;
  • 客户接收修改的数据并在其监视器上将该行显示出来。

UDP 实现

该应用程序客户端的代码:

UDPClient.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from socket import *

# serverName 为包含服务器的 IP 地址,或者是包含服务器的主机名
# serverPort 为服务器的端口号
serverName = 'hostname'
serverPort = 12000

# 创建客户的套接字
# 第一个参数指示了地址簇,第二个参数指示了套接字类型
# AF_INET 指示了底层网络使用了 IPv4
# SOCK_DGRAM 意味着它是一个 UDP 套接字
# 操作系统为我们指定客户套接字的端口号(隐式)
clientSocket = socket(AF_INET, SOCK_DGRAM)

message = raw_input('Input lowercase sentence:')

# sendto 方法为报文附上目的地址 (serverName, serverPort) 并向进程的套接字 clientSocket 发送结果分组
clientSocket.sendto(message.encode(), (serverName, serverPort))

# 客户等待接收来自服务器的数据
# 当一个来自因特网的分组到达该客户套接字时
# 该分组的数据被放置到变量 modifiedMessage 中
# 其源地址(服务器的 IP 地址和服务器的端口号)被放置到变量 serverAddress 中(程序实际不需要)
# 2048 为缓存长度
modifiedMessage, serverAddress = clientSocket.recvfrom(2048)

print(modifiedMessage.decode())

# 关闭套接字,关闭该进程
clientSocket.close()

该应用程序服务器端的代码:

UDPServer.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from socket import *

serverPort = 12000

# 创建服务器的套接字
serverSocket = socket(AF_INET, SOCK_DGRAM)

# 代码显式地为服务器套接字分配端口号 12000
# 任何人向位于该服务器的 IP 地址的端口 12000 发送一个分组,该分组将导向该套接字
serverSocket.bind(('', serverPort))

print("The server is ready to receive")

while True:
# 到达、处理、发回
message, clientAddress = serverSocket.recvfrom(2048)
modifiedMessage = message.decode().upper()
serverSocket.sendto(modifiedMessage.encode(), clientAddress)
# 维持 while 循环,等待另一个 UDP 分组到达

TCP 实现

该应用程序客户端的代码:

TCPClient.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from socket import *

serverName = 'hostname'
serverPort = 12000

# 创建客户的套接字
# SOCK_STREAM 意味着它是一个 TCP 套接字
# 操作系统为我们指定客户套接字的端口号(隐式)
clientSocket = socket(AF_INET, SOCK_STREAM)

# 发起客户和服务器之间的 TCP 连接
# 这行代码执行完后,执行三次握手,并在客户和服务器之间创建起一条 TCP 连接
clientSocket.connect((serverName, serverPort))

sentence = raw_input('Input lowercase sentence:')

# 并不像 UDP 一样显式地创建一个分组并为该分组附上目的地址
# 只是将 sentence 中的字节放入该 TCP 连接中去
clientSocket.send(sentence.encode())

# 等待接收来自服务器的字节
modifiedSentence = clientSocket.recv(1024)
print('From Server: ', modifiedSentence.decode())
clientSocket.close()

该应用程序服务器端的代码:

TCPServer.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from socket import *
serverPort = 12000

# 服务器创建一个 TCP 套接字
serverSocket = socket(AF_INET, SOCK_STREAM)

# 将服务器的端口号与该套接字关联起来
# 对 TCP 而言,serverSocket 相当于欢迎套接字
serverSocket.bind(('', serverPort))

# 服务器聆听来自客户的 TCP 连接请求,参数定义了请求连接的最大数(至少为 1)
serverSocket.listen(1)
print('The server is ready to receive')

while True:
# 收到来自客户的 TCP 连接请求,为 serverSocket 调用 accept() 方法
# 创建新套接字 connectionSocket,由这个特定的客户专用
# 建立 TCP 连接
connectionSocket, addr = serverSocket.accept()
sentence = connectionSocket.recv(1024).decode()
capitalizedSocket.send(capitalizedSentence.encode())
connectionSocket.close()
# serverSocket 保持打开

更深入的了解,从 Python 到 C

一些数据结构

in_addr

一个 IP 地址就是一个 32 位无符号整数。网络程序将 IP 地址存放在 IP 地址结构中。

1
2
3
4
/* IP address structure */
struct in_addr {
uint32_t s_addr; /* Address in network byte order (big-endian) */
}

socketaddr_in

socketaddr_in:
IP 地址和 Port 捆绑关系的数据结构(标示进程的端结点)

1
2
3
4
5
6
7
8
9
10
11
12
13
/* IP socket address structure */
struct socketaddr_in {
uint16_t sin_family; /* Protocol family (always AF_INET) */
uint16_t sin_port; /* Port number in network byte order */
struct in_addr sin_addr; /* IP address in network byte order */
unsigned char sin_zero[8]; /* Pad to sizeof(struct sockaddr) */
}

/* Generic socket address structure (for connect, bind, and accept) */
struct sockaddr {
uint16_t sa_family; /* Protocol family */
char sa_data[14]; /* Address data */
}

hostend

域名和 IP 地址的数据结构

1
2
3
4
5
6
7
8
struct hostent {
char* h_name; /* 主机域名 */
char** h_aliases; /* 域名别名 */
int h_addrtype;
int h_length; /* 地址长度 */
char** h_addr_list; /* IP 地址 */
#define h_addr h_addr_list[0];
}

作为调用域名解析函数时的参数
返回后,将 IP 地址拷贝到 sockaddr_in 的 IP 地址部分

C 客户端(UDP)

UDPclient.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/* client.c */
void main(int argc, char *argv[])
{
struct sockaddr_in sad; /* structure to hold an IP address */
int clientSocket; /* socket descriptor */
struct hostent *ptrh; /* pointer to a host table entry */

char Sentence[128];
char modifiedSentence[128];

host = argv[1]; port = atoi(argv[2]);

clientSocket = socket(PF_INET, SOCK_DGRAM, 0); /* 创建客户端 socket,没有连接到服务器 */

/* determine the server's address */
memset((char *)&sad,0,sizeof(sad)); /* clear sockaddr structure */
sad.sin_family = AF_INET; /* set family to Internet */
sad.sin_port = htons((u_short)port);
ptrh = gethostbyname(host);

/* Convert host name to IP address */
memcpy(&sad.sin_addr, ptrh->h_addr, ptrh->h_length);

gets(Sentence); /* Get input stream from user */

addr_len = sizeof(struct sockaddr);
n = sendto(clientSocket, Sentence, strlen(Sentence)+1,
(struct sockaddr *) &sad, addr_len); /* Send line to server */

n = recvfrom(clientSocket, modifiedSentence, sizeof(modifiedSentence),
(struct sockaddr *) &sad, &addr_len); /* Read line from server */

printf("FROM SERVER: %s\n”,modifiedSentence);

close(clientSocket); /* Close connection */
}

C 服务器(UDP)

UDPserver.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/* server.c */
void main(int argc, char *argv[])
{
struct sockaddr_in sad; /* structure to hold an IP address */
struct sockaddr_in cad;
int serverSocket; /* socket descriptor */
struct hostent *ptrh; /* pointer to a host table entry */

char clientSentence[128];
char capitalizedSentence[128];

port = atoi(argv[1]);

/* Create welcoming socket at port & Bind a local address */
serverSocket = socket(PF_INET, SOCK_DGRAM, 0);
memset((char *)&sad,0,sizeof(sad)); /* clear sockaddr structure */
sad.sin_family = AF_INET; /* set family to Internet */
sad.sin_addr.s_addr = INADDR_ANY; /* set the local IP address */
sad.sin_port = htons((u_short)port); /* set the port number */
bind(serverSocket, (struct sockaddr *)&sad, sizeof(sad));

while(1) {
n = recvfrom(serverSocket, clientSentence, sizeof(clientSentence), 0
(struct sockaddr *) &cad, &addr_len); /* Receive messages from clients */

/* capitalize Sentence and store the result in capitalizedSentence*/

n = sendto(serverSocket , capitalizedSentence, strlen(capitalizedSentence)+1,
(struct sockaddr *) &cad, &addr_len); /* Write out the result to socket */
}
/* End of while loop, loop back and wait for another client connection */
}

C 客户端(TCP)

TCPclient.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/* client.c */
/*
1. 建立 socket
2. 隐式捆绑 socket
3. 连接 socket
4. 写和读
5. 关闭 socket
*/
void main(int argc, char *argv[])
{
struct sockaddr_in sad; /* structure to hold an IP address of server */
int clientSocket; /* socket descriptor */
struct hostent *ptrh; /* pointer to a host table entry */

char Sentence[128];
char modifiedSentence[128];

host = argv[1]; port = atoi(argv[2]); /* 服务器的主机域名与端口号 */

/* Create client socket, connect to server */
clientSocket = socket(PF_INET, SOCK_STREAM, 0);
memset((char *)&sad,0,sizeof(sad)); /* clear sockaddr structure */
sad.sin_family = AF_INET; /* set family to Internet */
sad.sin_port = htons((u_short)port);
ptrh = gethostbyname(host); /* Convert host name to IP address */
memcpy(&sad.sin_addr, ptrh->h_addr, ptrh->h_length); /* 将IP地址拷贝到sad.sin_addr */
connect(clientSocket, (struct sockaddr *)&sad, sizeof(sad));

gets(Sentence); /* Get input stream from user */

n = write(clientSocket, Sentence, strlen(Sentence)+1); /* Send line to server */

n = read(clientSocket, modifiedSentence, sizeof(modifiedSentence));
/* Read line from server */
printf("FROM SERVER: %s\n”,modifiedSentence);

close(clientSocket); /* Close connection */
}

C 服务器(TCP)

TCPserver.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/* server.c */
/*
1. 建立 socket
2. 绑定 socket
3. 等待并建立连接 socket
4. 读和写
5. 关闭 (connection)socket
*/
void main(int argc, char *argv[])
{
struct sockaddr_in sad; /* structure to hold an IP address of server*/
struct sockaddr_in cad; /* client */
int welcomeSocket, connectionSocket; /* socket descriptor */
struct hostent *ptrh; /* pointer to a host table entry */

char clientSentence[128];
char capitalizedSentence[128];

port = atoi(argv[1]);

/* Create welcoming socket at port & Bind a local address */
welcomeSocket = socket(PF_INET, SOCK_STREAM, 0);
memset((char *)&sad,0,sizeof(sad)); /* clear sockaddr structure */
sad.sin_family = AF_INET; /* set family to Internet */
sad.sin_addr.s_addr = INADDR_ANY; /* set the local IP address */
sad.sin_port = htons((u_short)port); /* set the port number */
bind(welcomeSocket, (struct sockaddr *)&sad, sizeof(sad));

/* Specify the maximum number of clients that can be queued */
listen(welcomeSocket, 10)

while(1) {
connectionSocket = accept(welcomeSocket, (struct sockaddr *)&cad, &alen);
/* Wait, on welcoming socket for contact by a client */
n = read(connectionSocket, clientSentence, sizeof(clientSentence));

/* capitalize Sentence and store the result in capitalizedSentence*/

n = write(connectionSocket, capitalizedSentence, strlen(capitalizedSentence)+1);
/* Write out the result to socket */
close(connectionSocket);
/* End of while loop, loop back and wait for another client connection */
}