TCP是一种面向连接的、可靠的协议,有点像打电话,双方拿起电话互通身份之后就建立了连接,然后说话就行了,这边说的话那边保证听得到,并且是按说话的顺序听到的,说完话挂机断开连接。也就是说TCP传输的双方需要首先建立连接,之后由TCP协议保证数据收发的可靠性,丢失的数据包自动重发,上层应用程序收到的总是可靠的数据流,通讯之后关闭连接。
在TCP通讯中,如果一方收到另一方发来的段,读出其中的目的端口号,发现本机并没有任何进程使用这个端口,就会应答一个包含RST位的段给另一方。例如,服务器并没有任何进程使用8080端口,我们却用telnet客户端去连接它,服务器收到客户端发来的SYN段就会应答一个RST段,客户端的telnet程序收到RST段后报告错误Connection refused:
在数据传输过程中,ACK和确认序号是非常重要的,应用程序交给TCP协议发送的数据会暂存在TCP层的发送缓冲区中,发出数据包给对方之后,只有收到对方应答的ACK段才知道该数据包确实发到了对方,可以从发送缓冲区中释放掉了,如果因为网络故障丢失了数据包或者丢失了对方发回的ACK段,经过等待超时后TCP协议自动将发送缓冲区中的数据包重发。
由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。
客户端发出段7,FIN位表示关闭连接的请求。
服务器发出段8,应答客户端的关闭连接请求。
服务器发出段9,其中也包含FIN位,向客户端发送关闭连接请求。
客户端发出段10,应答服务器的关闭连接请求。
建立连接的过程是三方握手,而关闭连接通常需要4个段,服务器的应答和关闭连接请求通常不合并在一个段中,因为有连接半关闭的情况,这种情况下客户端关闭连接之后就不能再发送数据给服务器了,但是服务器还可以发送数据给客户端,直到服务器也关闭连接为止。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SERV_PORT 9527
void sys_err(const char *str){
perror(str);
exit(1);
}
int main(int argc,char *argv[]){
// 定义监听套接字、通信套接字
int lfd = 0, cfd = 0;
int ret,i;
char buf[BUFSIZ],client_IP[1024];
// 定义服务器端、客户端地址结构
struct sockaddr_in serv_addr,clit_addr;
// 客户端地址结构长度
socklen_t clit_addr_len;
// 初始化服务器端地址结构
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERV_PORT);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
// 监听套接字初始化以及创建失败处理
lfd = socket(AF_INET,SOCK_STREAM,0);
if(lfd == -1) {
sys_err("socket error");
}
// 将套接字绑定IP+端口
bind(lfd,(struct sockaddr *)&serv_addr, sizeof(serv_addr));
// 设置lfd套接字用于监听以及可以连接的客户端套接字数量
listen(lfd,128);
// 客户端地址结构长度初始化
clit_addr_len = sizeof(clit_addr);
// 本服务器端目前只可同时对一个客户端进行通信
while(1){
// 设置服务端套接字为被动状态,返回一个新的套接字用于通信以及创建失败处理
cfd = accept(lfd, (struct sockaddr *)&clit_addr, &clit_addr_len);
if(cfd == -1){
sys_err("accept error");
}
// 打印客户端ip以及端口
printf("client ip: %s port:%d\n", inet_ntop(AF_INET,&clit_addr.sin_addr.s_addr,client_IP,sizeof(client_IP)),ntohs(clit_addr.sin_port));
while(1) {
// 接收客户端传来的数据
ret = read(cfd,buf,sizeof(buf));
if(ret == 0) {
printf("the link is disconneted!\n");
break;
}
// 将客户端数据传入标准输出流中
write(STDOUT_FILENO,buf,ret);
// 将传来的数据变大写并传回到客户端
for(i = 0; i < ret; i++)
buf[i] = toupper(buf[i]);
write(cfd,buf,ret);
}
}
// close(lfd);
// close(cfd);
return 0;
}
客户端得知服务器端ip以及端口即可通信
#include
#include
#include
#include
#include
#include
#include
#include
#define SERV_PORT 9527
void sys_err(const char *str)
{
perror(str);
exit(1);
}
int main(int argc, char *argv[])
{
int cfd;
int conter = 10;
char buf[BUFSIZ];
//服务器地址结构
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERV_PORT);
// 转换ip地址由点分制到二进制
inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr);
// 初始化用于通信的套接字
cfd = socket(AF_INET, SOCK_STREAM, 0);
if (cfd == -1)
sys_err("socket error");
// 将客户端套接字与服务器端套接字连接
int ret = connect(cfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
if (ret != 0)
sys_err("connect err");
while (--conter) {
// 将数据通过套接字传输给服务器端
write(cfd, "hello\n", 6);
// 从套接字读取数据
ret = read(cfd, buf, sizeof(buf));
// 将数据输出至标准输出
write(STDOUT_FILENO, buf, ret);
sleep(1);
}
// 关闭客户端套接字
close(cfd);
return 0;
}
客户端: