TCP适用于对数据完整性和可靠性要求较高的应用场景,如文件传输、电子邮件、网页浏览等。
UDP适用于对实时性要求较高、数据丢失可容忍或应用自身提供可靠性机制的应用场景,如音频/视频流传输、在线游戏等。
需要根据具体应用需求选择使用TCP还是UDP。TCP提供可靠的有序数据传输,适合对数据完整性要求较高的场景;而UDP提供了更低的延迟和更高的实时性,适用于对实时性要求较高、数据丢失可容忍的场景。
(VM ubuntu编译Cpp:g++ -std=c++11 文件名 -o 生成文件名)
#include
#include
#include
#include
#include
#include
#define BUF_SIZE 1024
void error_handling(char *message);
int main(int argc, char *argv[]) {
int serv_sock, clnt_sock;
char message[BUF_SIZE];
int str_len, i;
struct sockaddr_in serv_adr, clnt_adr;
socklen_t clnt_adr_sz;
if (argc != 2) {
printf("Usage: %s \n", argv[0]);
exit(1);
}
serv_sock = socket(PF_INET, SOCK_STREAM, 0);//创建套接字
if (serv_sock == -1)
error_handling("socket() error");
memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family = AF_INET;
serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_adr.sin_port = htons(atoi(argv[1]));
if (bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)
error_handling("bind() error");
if (listen(serv_sock, 5) == -1)
error_handling("listen() error");
clnt_adr_sz = sizeof(clnt_adr);
for (i = 0; i < 5; i++) {//共调用五次accept函数,依次向五个客户端提供服务
clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);
if (clnt_sock == -1)
error_handling("accept() error");
else
printf("Connected client %d\n", i+1);
while ((str_len = read(clnt_sock, message, BUF_SIZE)) != 0)
write(clnt_sock, message, str_len);//实际完成回声服务的代码,原封不动的传输读取的字符串
close(clnt_sock);
}
close(serv_sock);//向5个客户端提供服务后关闭服务器端套接字并终止程序
return 0;
}
void error_handling(char *message) {
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
#include
#include
#include
#include
#include
#define BUF_SIZE 1024
void error_handling(const char *message);
int main(int argc, char *argv[]) {
int sock;
char message[BUF_SIZE];
int str_len;
struct sockaddr_in serv_adr;
if (argc != 3) {
std::cout << "Usage: " << argv[0] << " " << std::endl;
exit(1);
}
sock = socket(PF_INET, SOCK_STREAM, 0);
if (sock == -1)
error_handling("socket() error");
memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family = AF_INET;
serv_adr.sin_addr.s_addr = inet_addr(argv[1]);
serv_adr.sin_port = htons(atoi(argv[2]));
if (connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)//调用conndect函数
error_handling("connect() error!");
else
std::cout << "connected..........." << std::endl;
while (true) {
std::cout << "Input message (Q to quit): ";
std::cin.getline(message, BUF_SIZE);
if (strcmp(message, "q") == 0 || strcmp(message, "Q") == 0)
break;
write(sock, message, strlen(message));
str_len = read(sock, message, BUF_SIZE-1);
message[str_len] = '\0';
std::cout << "Message from server: " << message << std::endl;
}
close(sock);//调用close函数向相应套接字发送EOF(EOF意味着中断连接)
return 0;
}
void error_handling(const char *message) {
std::cerr << message << std::endl;
exit(1);
}
TCP/IP协议栈是一个网络通信的基本模型,它由四层协议组成,分别是应用层、传输层、网络层和链路层。
应用层:应用层提供了用户与网络之间的接口,负责处理特定的应用程序数据。常见的应用层协议有HTTP、FTP、SMTP等。
传输层:传输层主要负责在网络中传输数据。其中最重要的两个协议是TCP(传输控制协议)和UDP(用户数据报协议)。
TCP套接字经过传输层。TCP是一种面向连接的协议,提供可靠的数据传输。它通过建立连接、数据分段、流量控制、拥塞控制等机制来保证数据的可靠性和顺序性。TCP套接字在传输层使用IP地址和端口号进行标识。
UDP套接字也经过传输层。UDP是一种无连接的协议,不提供可靠性保证。它将数据打包成数据报发送,不关心数据是否能够到达目标地址。UDP套接字同样使用IP地址和端口号进行标识。
网络层(IP层):网络层负责将数据从源主机传输到目标主机,它使用IP协议进行数据包的路由选择和转发。IP协议为数据包分配唯一的IP地址。
链路层:链路层是网络通信的物理层和数据链路层接口,负责将网络层传输的数据转换成比特流进行物理传输。它包括了物理寻址、帧封装和错误检测等功能。
所以,TCP和UDP套接字在协议栈的层级结构上并无差异,都经过传输层。它们的主要区别在于TCP提供面向连接的可靠传输,而UDP提供无连接的不可靠传输。根据应用场景的需要,可以选择使用TCP或UDP来进行数据传输。
TCP/IP协议栈被分成多层的设计是出于以下几个原因,其中也与开放式系统相关:
模块化设计:将TCP/IP协议栈分成多层可以实现模块化的设计,每一层都有明确定义的功能和责任。这样可以提高系统的可维护性和可扩展性。如果需要对某一层进行修改或替换,只需关注特定层而无需改动其他层。
逻辑分离:将网络通信过程划分为不同的层级,可以实现逻辑上的分离。每一层专注于自身的任务,不需要关心其他层的具体实现。这种分离使得各层之间的接口定义清晰,降低了系统的复杂性。
标准化和互操作性:将TCP/IP协议栈分成多层使得每一层的功能和协议得到标准化。这样不同厂商、不同操作系统的设备都可以按照相同的标准实现对应的层,从而实现互操作性。这种开放式的设计使得不同设备和系统能够无缝地进行通信。
灵活性和可定制性:通过分层设计,可以根据具体需求选择性地使用不同层提供的功能。例如,某些应用场景可能只需要传输层和网络层的功能,可以选择性地使用这些层,而不需要实现其他层。这种灵活性和可定制性使得TCP/IP协议栈适应各种不同的网络和应用需求。
综上所述,将TCP/IP协议栈分成多层结构是为了实现模块化设计、逻辑分离、标准化和互操作性,以及提供灵活性和可定制性。这样的设计使得开放式系统中的不同设备和系统能够相互通信,并且能够根据具体需求进行定制和扩展。
服务器端调用 listen 函数后,客户端可以调用 connect 函数。
在TCP通信中,服务器端需要先调用listen函数来监听指定的端口,以便接受客户端的连接请求。listen函数使得服务器端处于监听状态,等待客户端连接。
一旦服务器端调用了listen函数,客户端就可以通过调用connect函数向服务器端发送连接请求。connect函数会建立与服务器端的连接,并进行三次握手来确保连接的可靠性。
所以,客户端需要在服务器端调用listen函数之后才能调用connect函数,以发起与服务器的连接。
连接请求等待队列是在服务器端调用listen
函数后创建的。它的作用是用于存储尚未被服务器端accept
函数接受的客户端连接请求。
当服务器端调用listen
函数后,它会开始监听指定的端口,并将该端口设置为监听状态,等待客户端的连接请求。如果有多个客户端同时向服务器端发送连接请求,而服务器端只能逐个处理这些请求,这时就需要使用连接请求等待队列。
连接请求等待队列允许服务器端在处理一个连接请求时,将其他连接请求暂时保存起来,以便后续处理。通过使用连接请求等待队列,可以确保当服务器端正在处理一个连接请求时,其他连接请求不会被丢失。
当服务器端调用accept
函数时,它从连接请求等待队列中取出一个连接请求进行处理。accept
函数会建立与客户端的连接,并返回一个新的套接字,服务器端可通过这个套接字与对应的客户端进行通信。
因此,连接请求等待队列与accept
函数密切相关。连接请求等待队列存储了尚未被接受的客户端连接请求,而accept
函数则负责从队列中取出连接请求并建立连接。通过连接请求等待队列和accept
函数的配合使用,服务器端能够管理和处理多个客户端的连接请求。
在客户端中,不需要调用bind()
函数来分配函数地址,是因为客户端一般不需要绑定特定的IP地址和端口号。客户端的套接字可以通过操作系统自动分配可用的临时端口,并自动关联客户端的IP地址。
当客户端创建套接字时,操作系统会自动选择一个可用的本地端口,并将其与套接字进行关联。这个过程通常称为"隐式绑定"(implicit binding)。
隐式绑定可以确保客户端使用的本地端口是唯一的,并且不会与其他应用程序冲突。对于大多数客户端应用程序而言,这种自动分配是足够的,无需显式调用bind()
函数。
需要注意的是,如果你想要在客户端使用特定的本地IP地址和端口号,或者需要进行端口重用等特殊操作,那么你可能需要调用bind()
函数来手动指定这些参数。但一般情况下,客户端并不需要手动调用bind()
函数来分配函数地址。