文章目录
- TCP网络编程概述
- 1. TCP协议的特点
- 2. TCP与UDP的差异
- 3. TCP编程流程
- TCP网络编程相关函数详解
- 1. `socket()`:创建套接字
- 参数说明:
- 返回值:
- 示例:
- 2. `connect()`:客户端连接服务器
- 参数说明:
- 返回值:
- 示例:
- 3. `bind()`:服务器绑定地址和端口
- 参数说明:
- 返回值:
- 示例:
- 4. `listen()`:监听连接请求
- 参数说明:
- 返回值:
- 示例:
- 5. `accept()`:接受客户端连接
- 参数说明:
- 返回值:
- 示例:
- 6. `send()`:发送数据
- 参数说明:
- 返回值:
- 示例:
- 7. `recv()`:接收数据
- 参数说明:
- 返回值:
- 示例:
- 8. `close()`:关闭连接
- 参数说明:
- 返回值:
- 示例:
- TCP客户端与服务端的实现案例
- TCP客户端实现
- TCP服务器实现
TCP网络编程概述
TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的传输层协议,广泛应用于网络通信。与UDP(User Datagram Protocol,用户数据报协议)不同,TCP提供了可靠的数据传输机制,确保数据能够完整、有序地从发送端传输到接收端。本文将详细介绍TCP协议的特点、TCP与UDP的差异、TCP编程流程以及客户端和服务器的实现方式。
1. TCP协议的特点
TCP具有以下主要特点:
- 面向连接:在通信前,TCP必须先建立连接。
- 有序号和确认机制:每个数据包都带有序号,接收方需要发送确认序号,确保数据有序接收。
- 排序、检错和失败重传:TCP对接收到的数据进行排序,检查数据的完整性,如有错误,会进行重传。
- 大文件传输:由于TCP能够分片并重新组装数据包,它特别适合大文件的可靠传输。
- 不支持广播和多播:与UDP不同,TCP不支持广播和多播,只支持点对点通信。
在TCP通信中,客户端和服务器的角色各不相同:
- TCP客户端:主动向服务器发起连接。
- TCP服务器:被动等待客户端连接。
2. TCP与UDP的差异
特点 | TCP | UDP |
---|---|---|
面向连接 | 是 | 否 |
可靠传输 | 是 | 否 |
顺序保证 | 是 | 否 |
传输效率 | 较低(需要连接、确认等) | 较高(无连接、无确认) |
数据传输大小限制 | 无 | 有(单个数据包有限制) |
广播/多播支持 | 否 | 是 |
TCP更适合需要可靠传输的应用场景,如文件传输、邮件等,而UDP更适合实时性要求高的应用,如视频、语音传输。
3. TCP编程流程
编写TCP程序时,主要流程如下:
- 创建套接字:使用
socket()
函数创建TCP套接字。 - 连接服务器(客户端)或绑定端口并监听连接(服务器)。
- 发送或接收数据:通过
send()
和recv()
函数进行数据交换。 - 关闭连接:使用
close()
函数关闭套接字。
TCP网络编程相关函数详解
在编写TCP程序时,通常会使用一系列网络函数来创建套接字、建立连接、发送/接收数据并关闭连接。下面将对TCP网络编程中常用的函数进行详细讲解,以帮助读者更好地理解每个函数的用途及其使用方法。
1. socket()
:创建套接字
socket()
函数是网络编程的基础,用于创建套接字(Socket)。套接字是网络通信的端点,类似于两台设备之间的通信通道。它的定义如下:
int socket(int domain, int type, int protocol);
参数说明:
domain
:指定通信使用的地址族,常用的有:AF_INET
:IPv4网络协议。AF_INET6
:IPv6网络协议。
type
:指定套接字类型,常用的有:SOCK_STREAM
:流式套接字,用于TCP连接。SOCK_DGRAM
:数据报套接字,用于UDP连接。
protocol
:一般为0,表示使用默认协议(TCP或UDP)。
返回值:
- 成功:返回套接字的文件描述符。
- 失败:返回-1,并设置
errno
。
示例:
int sock = socket(AF_INET, SOCK_STREAM, 0); // 创建TCP套接字
if (sock < 0) {
perror("socket creation failed");
}
2. connect()
:客户端连接服务器
connect()
函数用于客户端主动向服务器发起连接请求。在TCP连接中,客户端通过该函数连接指定的服务器。
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数说明:
sockfd
:客户端套接字描述符。addr
:服务器的地址结构,通常为struct sockaddr_in
。addrlen
:地址结构的大小。
返回值:
- 成功:返回0。
- 失败:返回-1,并设置
errno
。
示例:
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8000); // 服务器端口号
server_addr.sin_addr.s_addr = inet_addr("10.35.184.221"); // 服务器IP地址
if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) != 0) {
perror("connect failed");
}
3. bind()
:服务器绑定地址和端口
bind()
函数用于将套接字绑定到指定的IP地址和端口号。服务器需要通过bind()
来指定其服务的地址和端口。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数说明:
sockfd
:服务器套接字描述符。addr
:服务器地址结构,通常为struct sockaddr_in
。addrlen
:地址结构的大小。
返回值:
- 成功:返回0。
- 失败:返回-1,并设置
errno
。
示例:
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(9000); // 绑定端口9000
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 绑定本机所有IP
if (bind(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) != 0) {
perror("bind failed");
}
4. listen()
:监听连接请求
服务器通过listen()
函数来监听客户端的连接请求,进入监听状态,准备接受客户端的连接。
int listen(int sockfd, int backlog);
参数说明:
sockfd
:服务器套接字描述符。backlog
:连接队列的大小,表示服务器可以处理的等待连接的客户端数量。
返回值:
- 成功:返回0。
- 失败:返回-1,并设置
errno
。
示例:
if (listen(sock, 5) != 0) { // 最大连接等待队列长度为5
perror("listen failed");
}
5. accept()
:接受客户端连接
accept()
函数用于服务器从连接队列中取出一个客户端连接,生成一个新的套接字,用于和该客户端进行通信。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数说明:
sockfd
:服务器监听套接字。addr
:客户端地址结构,用于存储连接的客户端信息。addrlen
:地址结构的大小。
返回值:
- 成功:返回一个新的已连接套接字描述符,用于与客户端通信。
- 失败:返回-1,并设置
errno
。
示例:
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int client_sock = accept(sock, (struct sockaddr *)&client_addr, &client_len);
if (client_sock < 0) {
perror("accept failed");
}
6. send()
:发送数据
send()
函数用于向指定的套接字发送数据。
ssize_t send(int sockfd, const void *buffer, size_t length, int flags);
参数说明:
sockfd
:套接字描述符。buffer
:指向需要发送数据的缓冲区。length
:要发送的数据长度。flags
:通常为0,可选其他标志位。
返回值:
- 成功:返回发送的字节数。
- 失败:返回-1,并设置
errno
。
示例:
char message[] = "Hello, TCP Server!";
if (send(sock, message, strlen(message), 0) < 0) {
perror("send failed");
}
7. recv()
:接收数据
recv()
函数用于从指定的套接字接收数据。
ssize_t recv(int sockfd, void *buffer, size_t length, int flags);
参数说明:
sockfd
:套接字描述符。buffer
:指向接收数据的缓冲区。length
:缓冲区大小。flags
:通常为0,可选其他标志位。
返回值:
- 成功:返回接收到的字节数。
- 失败:返回-1,并设置
errno
。 - 如果连接被关闭,返回0。
示例:
char buffer[128];
ssize_t bytes_received = recv(sock, buffer, sizeof(buffer), 0);
if (bytes_received > 0) {
buffer[bytes_received] = '\0'; // 确保数据是以字符串形式输出
printf("Received data: %s\n", buffer);
} else if (bytes_received == 0) {
printf("Connection closed by peer\n");
} else {
perror("recv failed");
}
8. close()
:关闭连接
close()
函数用于关闭指定的套接字,释放相关资源。
int close(int sockfd);
参数说明:
sockfd
:需要关闭的套接字描述符。
返回值:
- 成功:返回0。
- 失败:返回-1,并设置
errno
。
示例:
close(sock); // 关闭套接字
TCP客户端与服务端的实现案例
TCP客户端实现
在TCP客户端编程中,客户端主动发起与服务器的连接。以下是一个基本的TCP客户端代码示例:
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
typedef struct sockaddr_in addr_in;
typedef struct sockaddr addr;
int main(int argc, char const *argv[]) {
// 创建TCP套接字
int sock = socket(AF_INET, SOCK_STREAM, 0);
// 连接TCP服务器
addr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8000);
server_addr.sin_addr.s_addr = inet_addr("10.35.184.221");
if (connect(sock, (addr *)&server_addr, sizeof(server_addr)) != 0) {
perror("connect");
return -1;
}
printf("TCP server connect OK\n");
// 每2秒发送一次数据
int n = 0;
while (1) {
char data[] = "hi, tcp server!";
if (send(sock, data, strlen(data), 0) > 0) {
printf("(%d)发送成功!\n", ++n);
}
sleep(2);
}
close(sock);
return 0;
}
TCP服务器实现
TCP服务器是被动的,等待客户端连接。在实现中,服务器需要首先绑定地址并监听客户端连接,接着通过accept()
函数接受客户端的连接。以下是一个简单的单聊TCP服务器实现:
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
typedef struct sockaddr_in addr_in;
typedef struct sockaddr addr;
typedef struct {
int sock;
char ip[INET_ADDRSTRLEN];
} client_info;
void *readTask(void *arg) {
client_info *info = (client_info *)arg;
while (1) {
char buf[128] = "";
ssize_t len = recv(info->sock, buf, 128, 0);
if (len > 0) {
printf("%s: %s\n", info->ip, buf);
}
}
}
void *sendTask(void *arg) {
client_info *info = (client_info *)arg;
while (1) {
char buf[128] = "";
fgets(buf, 128, stdin);
buf[strlen(buf)-1] = 0;
send(info->sock, buf, strlen(buf), 0);
if (strncmp(buf, "bye", 3) == 0) break;
}
}
int main(int argc, char const *argv[]) {
if (argc != 2) return -1;
// 创建TCP套接字
int sock = socket(AF_INET, SOCK_STREAM, 0);
// 设置服务器地址
addr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(atoi(argv[1]));
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(sock, (addr *)&server_addr, sizeof(server_addr)) != 0) {
perror("bind");
return -1;
}
listen(sock, 1000);
printf("TCP server running\n");
addr_in client_addr;
socklen_t client_addr_len = sizeof(client_addr);
int client_sock = accept(sock, (addr *)&client_addr, &client_addr_len);
if (client_sock > 0) {
client_info info;
info.sock = client_sock;
inet_ntop(AF_INET, &client_addr.sin_addr, info.ip, INET_ADDRSTRLEN);
printf("Client connected: %s\n", info.ip);
pthread_t read_tid, send_tid;
pthread_create(&read_tid, NULL, readTask, &info);
pthread_create(&send_tid, NULL, sendTask, &info);
pthread_join(send_tid, NULL);
}
close(sock);
return 0;
}
通过上述TCP网络编程的介绍和实例代码,读者可以掌握如何使用TCP协议进行可靠的数据通信,并根据实际需求实现功能丰富的网络应用程序。