网络编程是一种涉及计算机网络的软件开发技术,它允许不同计算机之间的通信和数据交换。在网络编程中,TCP/IP协议是基础,它定义了数据如何在网络上进行传输。
TCP/IP协议
TCP/IP(Transmission Control Protocol/Internet Protocol)是一组通信协议,它是互联网通信的基础。主要包括TCP和IP两个子协议。
IP地址和协议
-
IP地址: 每个主机都有一个唯一的32位IP地址,用于在网络中标识其位置。IP地址分为两部分,即网络部分和主机部分。
-
IP协议: 用于在IP主机之间发送和接收数据包。数据包由IP头、发送方IP地址、接收方IP地址以及数据组成。
路由器
-
TTL(Time To Live): IP包中的一个8位生存时间计数,防止数据包在网络中无限循环。每经过一个路由器,TTL减小1,如果减小到0,包会被丢弃。
-
路由器: 用于接收和转发数据包,将数据包从源主机传输到目标主机。
UDP协议
UDP(User Datagram Protocol)是在IP上运行的协议,用于发送和接收数据报。相对于TCP,UDP不保证可靠性,但速度更快。
- UDP特点: 快速高效,不保证数据可靠性。常用于实时应用,如视频流、音频等。
TCP协议
TCP(Transmission Control Protocol)是一种面向连接的协议,用于发送和接收数据流。相对于UDP,TCP提供了可靠的数据传输。
-
端口编号: 多个应用程序可以同时使用TCP,每个应用程序由主机IP、协议(TCP/IP)、端口号唯一标识。
-
TCP连接: 类似于电话连接,通过建立连接来传输数据。通过
socket()
创建套接字,connect()
建立连接,send()
和recv()
进行数据传输。
网络字节序
计算机可以使用大端字节序或小端字节序,为了在网络中正确传输数据,需要进行字节序转换。
- 大端/小端: 不同计算机体系结构对字节的存储顺序不同。网络字节序通常使用大端字节序。
数据流在TCP/IP网络中的传输
数据在网络中的传输涉及多个层级,从应用层到链路层,分别添加TCP或UDP报头、IP报头、以及网络链路层的帧。
套接字编程
套接字是实现网络编程的接口,使用一系列C语言库函数和系统调用来实现。在UNIX/Linux系统中,套接字API提供了socket()
、bind()
等函数。
-
套接字地址: 使用
struct sockaddr_in
定义,包括协议类型、端口号、IP地址等信息。 -
UDP套接字: 使用
sendto()
和recvfrom()
进行数据传输。 -
TCP套接字: 使用
listen()
监听连接请求,accept()
接受连接,connect()
建立连接,send()
和recv()
进行数据传输。
UDP服务器-客户端程序
UDP服务器 (udp_server.c)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#define BUFLEN 256 // 缓冲区最大长度
#define PORT 1234 // 服务器端口号
char line[BUFLEN];
struct sockaddr_in me, client;
int sock, rlen, clen = sizeof(client);
int main() {
printf("1. 创建一个UDP套接字\n");
sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
printf("2. 填充服务器地址和端口号\n");
memset((char *)&me, 0, sizeof(me));
me.sin_family = AF_INET;
me.sin_port = htons(PORT);
me.sin_addr.s_addr = htonl(INADDR_ANY); // 使用本地地址
printf("3. 将套接字绑定到服务器IP和端口\n");
bind(sock, (struct sockaddr*)&me, sizeof(me));
printf("4. 等待数据报\n");
while (1) {
memset(line, 0, BUFLEN);
printf("UDP服务器: 等待数据报\n");
// recvfrom() 获取客户端IP和端口信息
rlen = recvfrom(sock, line, BUFLEN, 0, (struct sockaddr *)&client, &clen);
printf("从 [主机:端口] = [%s:%d] 收到一个数据报\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
printf("rlen=%d: line=%s\n", rlen, line);
printf("发送回复\n");
sendto(sock, line, rlen, 0, (struct sockaddr*)&client, clen);
}
}
UDP客户端 (udp_client.c)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#define SERVER_HOST "127.0.0.1" // 默认服务器IP: 本地主机
#define SERVER_PORT 1234 // 服务器端口号
#define BUFLEN 256 // 缓冲区最大长度
char line[BUFLEN];
struct sockaddr_in server;
int sock, rlen, slen = sizeof(server);
int main() {
printf("1. 创建一个UDP套接字\n");
sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
printf("2. 填写服务器地址和端口号\n");
memset((char *)&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(SERVER_PORT);
inet_aton(SERVER_HOST, &server.sin_addr);
while (1) {
printf("输入一行:");
fgets(line, BUFLEN, stdin);
line[strlen(line) - 1] = 0; // 去除末尾的换行符
printf("向服务器发送数据\n");
sendto(sock, line, strlen(line), 0, (struct sockaddr *)&server, slen);
memset(line, 0, BUFLEN);
printf("尝试从服务器接收一行\n");
rlen = recvfrom(sock, line, BUFLEN, 0, (struct sockaddr *)&server, &slen);
printf("rlen=%d: line=%s\n", rlen, line);
}
}
TCP回显服务器-客户端程序
TCP服务器 (tcp_server.c)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#define MAX 256
#define SERVER_PORT 1234
struct sockaddr_in server_addr, client_addr;
int mysock, csock; // socket描述符
int r, len, n; // 辅助变量
int server_init() {
printf("================== 服务器初始化 ======================\n");
// 创建TCP套接字
printf("1: 创建一个TCP流套接字\n");
mysock = socket(AF_INET, SOCK_STREAM, 0);
if (mysock < 0) {
printf("socket调用失败\n");
exit(1);
}
// 填写服务器地址信息
printf("2: 用主机IP和端口号信息填充server_addr\n");
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 本地主机
server_addr.sin_port = htons(SERVER_PORT);
// 将套接字绑定到服务器地址
printf("3: 将套接字绑定到服务器地址\n");
r = bind(mysock, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (r < 0) {
printf("绑定失败\n");
exit(3);
}
printf(" 主机名 = %s 端口 = %d\n", SERVER_HOST, SERVER_PORT);
printf("4: 服务器正在监听....\n");
listen(mysock, 5); // 队列长度为5
printf("=================== 初始化完成 =======================\n");
}
int main() {
char line[MAX];
server_init();
while (1) {
// 尝试接受客户端请求
printf("服务器: 正在接受新连接....\n");
// 尝试接受客户端连接,将描述符保存在csock中
len = sizeof(client_addr);
csock = accept(mysock, (struct sockaddr *)&client_addr, &len);
if (csock < 0) {
printf("服务器: 接受错误\n");
exit(1);
}
printf("服务器: 接受到来自\n");
printf("-----------------------------------------------\n");
printf("客户端: IP=%s 端口=%d\n",
inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port));
printf("-----------------------------------------------\n");
// 处理循环:client_sock <== data ==> client
while (1) {
n = read(csock, line, MAX);
if (n == 0) {
printf("服务器: 客户端断开连接,服务器循环\n");
close(csock);
break;
}
// 显示收到的字符串
printf("服务器: 读取 n=%d 字节; line=%s\n", n, line);
// 将字符串回送给客户端
n = write(csock, line, MAX);
printf("服务器: 写入 n=%d 字节; ECHO=%s\n", n, line);
printf("服务器: 准备处理下一个请求\n");
}
}
}
TCP客户端
(tcp_client.c)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#define MAX 256
#define SERVER_HOST "localhost"
#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 1234
struct sockaddr_in server_addr;
int sock, r;
int client_init() {
printf("======= 客户端初始化 ==========\n");
printf("1 : 创建一个TCP套接字\n");
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
printf("socket调用失败\n");
exit(1);
}
printf("2 : 用服务器的IP和端口号信息填充server_addr\n");
// 初始化server_addr结构
server_addr.sin_family = AF_INET; // 用于TCP/IP
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 本机主机
server_addr.sin_port = htons(SERVER_PORT); // 端口号1234
printf("3 : 连接到服务器....\n");
r = connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (r < 0) {
printf("连接失败\n");
exit(3);
}
printf("4 : 连接成功\n");
printf("-------------------------------------------------------\n");
printf("服务器主机名=%s 端口号=%d\n", SERVER_HOST, SERVER_PORT);
printf("-------------------------------------------------------\n");
printf("========= 初始化完成 ==========\n");
}
int main() {
int n;
char line[MAX], ans[MAX];
client_init();
printf("******** 处理循环 *********\n");
while (1) {
printf("输入一行:");
bzero(line, MAX); // 清零line[]
fgets(line, MAX, stdin); // 从stdin中获取一行
line[strlen(line) - 1] = 0; // 去掉末尾的\n
if (line[0] == 0) // 如果是空行,退出
exit(0);
// 发送数据到服务器
n = write(sock, line, MAX);
printf("客户端: 写入 n=%d 字节; line=%s\n", n, line);
// 从sock中读取一行并显示
n = read(sock, ans, MAX);
printf("客户端: 读取 n=%d 字节; 回显=%s\n", n, ans);
}
}
上述程序包含了UDP服务器-客户端和TCP服务器-客户端的代码。这些代码通过套接字编程实现了基本的网络通信。请确保在运行这些程序之前,没有其他程序占用相同的端口号。
苏格拉底挑战