TCP服务端/客服端默认函数调用顺序
服务端顺序如下:
1,socket() 创建套接字
2,bind() 分配套接字地址
3,listen() 等待连接请求状态
4,accept() 连接(阻断函数,直到有连接请求后才返回)
5,read()/write() 数据交换
6,close() 断开连接
客服端顺序如下:
1,socket() 创建套接字
2,connect() 请求连接(要在listen()调用之后)
3,read()/write() 数据交换
4,close() 断开连接
下面再来分别讲讲上面提到的前面章节没讲解过的新接口:
int listen(int sock, int backlog);
sock:服务端套接字文件描述符(监听套接字)
backlog:连接请求等待队列的大小。即客服端请求的连接会先放入这个队列中排队,等待处理。如果队列长度为5,则表示最多使5个连接请求进入队列。
int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);
sock:服务端套接字的文件描述符
addr:保存发起连接请求的客服端地址信息的变量
addrlen:保存客服端地址长度
注:这个函数就是受理连接请求等待队列中待处理的客服端连接请求。它是个阻断函数,只有队列中有请求才会返回,并在其内部自动产生用于数据I/O的套接字。
int connect(int sock, struct sockaddr *servaddr, socklen_t addrlen);
sock:客服端套接字文件描述符
servaddr:保存目标服务端地址信息的变量
addrlen:服务端地址变量长度
注释:accept()要获取客服端的地址信息,但客服端压根就没有bind()分配地址这一步,那么客服端套接字地址信息在哪呢?其实是在调用connect()时,由操作系统自动分配的,IP用的主机的IP,端口随机。
ssize_t write(int fd, const void *buf, size_t nbytes);
fd:数据传输对象的文件描述符
buf:保存要传输数据的缓冲地址值
nbytes:要传输数据的字节数
ssize_t read(int fd, void *buf, size_t nbytes);
fd:数据接收数据的文件描述符
buf:保存要接收数据的缓冲地址值
nbytes:要接收数据的字节数
注释:
TCP套接字中的I/O缓冲:write()发送数据与read()接收数据它们各自对应有一个输出缓冲与输入缓冲。发送数据是先发往这个输出缓冲,再在适当时候(不管是分别传送还是一次性传送)发往read()对应的输入缓冲,而接收数据也就是从这个输入缓冲中取。
TCP套接字内部工作原理
1,TCP套接字从创建到消失一般分为如下3个过程:
与对方套接字建立连接
与对方套接字进行数据交换
断开与对方套接字的连接
2,与对方套接字建立连接模拟过程如下:
首先,请求连接的主机A向主机B传递如下信息:[SYN] SEQ: 1000, ACK: -;该消息中SEQ为1000,ACK为空,而SEQ为1000的含义如下:“现传递的数据包序号为1000,如果接收无误,请通知我向您传递1001号数据包”这是首次请求连接时使用的消息,又称SYN(表示收发数据前传输的同步消息)。
然后,主机B向主机A传递如下消息:[SYN + ACK] SEQ: 2000, ACK:1001;此时SEQ为2000,ACK为1001,而SEQ为2000的含义如下:“现传递的数据包序号为2000,如果接收无误,请通知我向您传递2001号数据包”而ACK为1001的含义如下:“刚才传输的SEQ为1000的数据包接收无误,现在请传递SEQ为1001的数据包”对主机A首次传输的数据包的确认消息(ACK:1001)和为主机B传输数据做准备的同步消息(SEQ:2000)捆绑发送,因此,此种类型的消息又称SYN+ACK.
最后观察主机A向主机B传输的消息:[ACK] SEQ:1001, ACK:2001;此时数据包传递如下消息:“已正确收到传输的SEQ为2000的数据包,现在可以传输SEQ为2001的数据包”这样就传输了添加ACK 2001的ACK消息。至此,主机A和主机B才确认建立了连接。这个连接经过了3次消息传递,因此,该过程又称三次握手。
3,与对方主机的数据交换
通过第一步三次握手过程完成了数据交换前的准备工作,下面再来看看TCP具体怎么交换数据的:假设主机A分2次向主机B传递200字节的过程。首先,主机A通过1个数据包发送100个字节的数据,数据包的SEQ为1200(SEQ 1200 100 byte data)。然后主机B向主机A发送ACK 1301消息确认(ACK号 = SEQ号 + 传递的字节数 + 1)。同上,主机A向主机B发送剩余的100字节数据(SEQ 1301 100 byte data),最后主机B向主机A确认收到ACK 1402。这就完成了200字节数据的传输。TCP就是这样,发送-确认-发送,有序安全的协议。
TCP套接字在SEQ发送了一个数据包后,会启动一个计时器以等待ACK回答,如果一定时间没有收到回复,它就会重传。
4,断开与套接字的连接
TCP套接字的结束过程也非常优雅,如果对方还有数据需要传输时直接断掉连接会出问题,所以断开连接时需要双方协商。过程如下:
先由主机A向主机B传递断开消息,然后,主机B发出确认消息 + 向主机A传递可以断开连接的消息,最后,主机A同样发出确认消息。这个过程经历4个阶段,因此又称四次握手。消息模拟过程是这样:[FIN] SEQ 5000 ACK - –> [ACK] SEQ 7500 ACK 5001 –> [FIN] SEQ 7501 ACK 5001 –> [ACK] SEQ 5001 ACK 7502。
实现回声服务端/客服端
什么是回声服务端/客服端?顾名思义,服务端将客服端传输的字符串数据原封不动地传回客服端,就像回声一样。
服务端代码
//
// main.cpp
// hello_server
//
// Created by app05 on 15-7-6.
// Copyright (c) 2015年 app05. All rights reserved.
//
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 1024
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
int main(int argc, const 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 <port> \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++) {
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);
return 0;
}
客服端代码
//
// main.cpp
// hello_client
//
// Created by app05 on 15-7-6.
// Copyright (c) 2015年 app05. All rights reserved.
//
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 1024
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
int main(int argc, const char * argv[]) {
int sock;
char message[BUF_SIZE];
int str_len, recv_len, recv_cnt;
struct sockaddr_in serv_adr;
if(argc != 3)
{
printf("Usage: %s <IP> <port> \n", argv[0]);
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)
error_handling("connect() error");
else
puts("Connected ...............");
while (1) {
fputs("Input message(Q to quit): ", stdout);
fgets(message, BUF_SIZE, stdin);
if (!strcmp(message, "q\n") || !strcmp(message, "Q\n"))
break;
str_len = write(sock, message, strlen(message));
/*这里需要循环读取,因为TCP没有数据边界,不循环读取可能出现一个字符串一次发送
但分多次读取而导致输出字符串不完整*/
recv_len = 0;
while (recv_len < str_len) {
recv_cnt = read(sock, &message[recv_len], BUF_SIZE - 1);
if(recv_cnt == -1)
error_handling("read() error");
recv_len += recv_cnt;
}
message[recv_len] = 0;
printf("Message from server: %s", message);
}
close(sock);
return 0;
}