目录
TCP/IP协议的五层模型
TCP/IP协议是互联网的核心协议,它的五层模型分别为:
-
应用层:应用层协议处理网络应用程序之间的数据交换,如FTP、SMTP、HTTP等。
-
传输层:传输层协议为应用层提供可靠的端到端通信,如TCP和UDP。
-
网络层:网络层协议负责将分组从源节点传输到目的节点,如IP协议。
-
数据链路层:数据链路层协议负责在物理层上提供数据传输服务,如以太网协议。
-
物理层:物理层协议负责处理数据在物理媒介上传输的细节,如电信号和传输介质。
总体来说,TCP/IP五层模型是一种将通信协议按照功能分层的方法,从而为网络设计者提供了一个框架,用于在不同层次间进行协同工作。每一层都有自己特定的功能和职责,因此,TCP/IP协议可以实现不同的应用程序之间的通信。
TCP通信的实现过程
TCP通信的实现过程主要包括三个阶段:建立连接、数据传输和连接释放。
- 建立连接
在TCP通信开始之前,发送方和接收方需要先建立连接,以便在传输数据时能够互相识别。
a. 三次握手
发送方首先向接收方发送一个SYN(同步)包,用于请求建立连接。接收方收到SYN包后,回复一个ACK(确认)包,表示接收到请求。同时,接收方也向发送方发送一个SYN-ACK包,表示接收方准备好了建立连接。发送方收到SYN-ACK包后,回复一个ACK包,表示连接建立成功。
这个过程被称为“三次握手”,是为了确保双方都能正确地识别对方并建立连接。
b. 建立连接后的数据传输
连接建立后,发送方可以向接收方发送数据,数据会按照TCP协议进行分组、封装、校验和传输。接收方会根据TCP协议对数据进行解封、校验和重组,确保数据的正确性。
- 数据传输
在建立连接后,发送方可以向接收方发送数据。TCP协议保证数据的可靠性,通过以下几个步骤实现:
a. 分组
发送的数据会被切分成多个数据包,每个数据包称为分组。
b. 封装
在发送数据时,TCP会将每个分组封装成一个TCP报文段,添加报文头和报文尾,以便接收方进行识别和处理。
c. 校验
TCP协议会对每个TCP报文段进行校验和处理,确保数据的正确性。
d. 重传
如果发送的某个分组丢失,或者接收方没有正确地接收到某个分组,TCP协议会进行重传,确保数据能够被正确地传输。
- 连接释放
当数据传输完成后,发送方和接收方需要关闭连接,释放资源。
a. 四次挥手
发送方发送一个FIN包,表示数据已经发送完毕,准备关闭连接。接收方收到后回复一个ACK包,表示已经收到FIN包。当接收方也没有数据需要发送时,发送一个FIN包,请求断开连接。发送方收到FIN包后,回复一个ACK包,表示已经收到请求。这个过程被称为“四次挥手”,是为了确保双方都能正确地关闭连接。
b. 等待2MSL
在关闭连接后,需要等待2MSL(最长报文存活时间)的时间,确保所有的报文都被正确地传输完毕。过了2MSL时间后,连接才算真正关闭。
以上就是TCP通信的实现过程。通过这些步骤,TCP协议能够确保数据传输的可靠性和正确性,同时也能保证连接的有效性和安全性。
TCP编程的函数详解
TCP编程中常用的函数有:
socket()函数
函数原型:int socket(int domain, int type, int protocol)
该函数用于创建一个新的TCP套接字,成功时返回文件描述符,出错时返回 -1。
参数说明:
- domain:协议族,通常使用 AF_INET(IPv4)或 AF_INET6(IPv6)。
- type:套接字类型,通常使用 SOCK_STREAM(面向连接的套接字)。
- protocol:协议类型,通常设为 0,由系统自动根据 domain 和 type 来选择协议。
bind()函数
函数原型:int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
该函数用于将一个本地地址(IP地址和端口号)绑定到一个套接字,使得网络上的其他主机能够通过该套接字连接到这个本地地址。成功时返回 0,出错时返回 -1。
参数说明:
- sockfd:需要绑定的套接字描述符。
- addr:一个 sockaddr 类型的结构体,包含了要绑定的本地地址信息。
- addrlen:addr 结构体的长度。
listen()函数
函数原型:int listen(int sockfd, int backlog)
该函数用于将一个套接字设为监听状态,使得该套接字能够接受来自其他主机的连接请求。成功时返回 0,出错时返回 -1。
参数说明:
- sockfd:需要监听的套接字描述符。
- backlog:请求队列的长度,即可以同时接受多少个连接请求。
accept()函数
函数原型:int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
该函数用于接受一个等待的连接请求,并返回一个新的套接字描述符,该描述符用于和客户端进行数据通信。成功时返回新的套接字描述符,出错时返回 -1。
参数说明:
- sockfd:需要接受连接请求的套接字描述符。
- addr:一个 sockaddr 类型的结构体,用于存储与客户端连接的远程地址信息。
- addrlen:addr 结构体的长度。
connect()函数
函数原型:int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
该函数用于向指定的服务器地址发起连接请求。成功时返回 0,出错时返回 -1。
参数说明:
- sockfd:需要连接的套接字描述符。
- addr:一个 sockaddr 类型的结构体,包含了要连接的服务器地址信息。
- addrlen:addr 结构体的长度。
send()函数
函数原型:ssize_t send(int sockfd, const void *buf, size_t len, int flags)
该函数用于向套接字发送数据。成功时返回发送的字节数,出错时返回 -1。
参数说明:
- sockfd:需要发送数据的套接字描述符。
- buf:指向待发送数据的缓冲区。
- len:待发送数据的长度。
- flags:发送数据的选项参数,通常设为 0。
recv()函数
函数原型:ssize_t recv(int sockfd, void *buf, size_t len, int flags)
该函数用于从套接字接收数据。成功时返回接收的字节数,出错时返回 -1。
参数说明:
- sockfd:需要接收数据的套接字描述符。
- buf:指向接收数据的缓冲区。
- len:缓冲区长度。
- flags:接收数据的选项参数,通常设为 0。
close()函数
函数原型:close(int sockfd)
该函数用于关闭一个打开的套接字。
参数说明:
- sockfd:需要接收数据的套接字描述符。
以上就是TCP编程中常用的函数及其参数的详细介绍。
TCP编程的实现
服务器端:
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <pthread.h>
#define BACKLOG 5
void *ClinetHandle(void *arg);
int main(int argc, char *argv[])
{
int fd, newfd;
struct sockaddr_in addr, clint_addr;
pthread_t tid;
socklen_t addrlen = sizeof(clint_addr);
if(argc < 3){
fprintf(stderr, "%s<addr><port>\n", argv[0]);
exit(0);
}
/*创建套接字*/
fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd < 0){
perror("socket");
exit(0);
}
addr.sin_family = AF_INET;
addr.sin_port = htons( atoi(argv[2]) );
if ( inet_aton(argv[1], &addr.sin_addr) == 0) {
fprintf(stderr, "Invalid address\n");
exit(EXIT_FAILURE);
}
/*地址快速重用*/
int flag=1,len= sizeof (int);
if ( setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &flag, len) == -1) {
perror("setsockopt");
exit(1);
}
/*绑定通信结构体*/
if(bind(fd, (struct sockaddr *)&addr, sizeof(addr) ) == -1){
perror("bind");
exit(0);
}
/*设置套接字为监听模式*/
if(listen(fd, BACKLOG) == -1){
perror("listen");
exit(0);
}
while(1){
/*接受客户端的连接请求,生成新的用于和客户端通信的套接字*/
newfd = accept(fd, (struct sockaddr *)&clint_addr, &addrlen);
if(newfd < 0){
perror("accept");
exit(0);
}
printf("addr:%s port:%d\n", inet_ntoa(clint_addr.sin_addr), ntohs(clint_addr.sin_port) );
pthread_create(&tid, NULL, ClinetHandle, &newfd);
pthread_detach(tid);
}
close(fd);
return 0;
}
void *ClinetHandle(void *arg){
int ret;
char buf[BUFSIZ] = {};
int newfd = *(int *)arg;
while(1){
//memset(buf, 0, BUFSIZ);
bzero(buf, BUFSIZ);
ret = read(newfd, buf, BUFSIZ);
if(ret < 0)
{
perror("read");
exit(0);
}
else if(ret == 0)
break;
else
printf("buf = %s\n", buf);
}
printf("client exited\n");
close(newfd);
return NULL;
}
客户端:
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#define BACKLOG 5
int main(int argc, char *argv[])
{
int fd;
struct sockaddr_in addr;
char buf[BUFSIZ] = {};
if(argc < 3){
fprintf(stderr, "%s<addr><port>\n", argv[0]);
exit(0);
}
/*创建套接字*/
fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd < 0){
perror("socket");
exit(0);
}
addr.sin_family = AF_INET;
addr.sin_port = htons( atoi(argv[2]) );
if ( inet_aton(argv[1], &addr.sin_addr) == 0) {
fprintf(stderr, "Invalid address\n");
exit(EXIT_FAILURE);
}
/*向服务端发起连接请求*/
if(connect(fd, (struct sockaddr *)&addr, sizeof(addr) ) == -1){
perror("connect");
exit(0);
}
while(1){
printf("Input->");
fgets(buf, BUFSIZ, stdin);
write(fd, buf, strlen(buf) );
}
close(fd);
return 0;
}