TCP通信
套接字:
Socket将运输层的很多复杂操作封装成一些简单的接口,来让应用层调用,实现进程再网络中的通信
本质上是一个非负数,是一种特殊的文件描述符
创建套接字:int socket(int domain, int type, int protocol);
参数:
domain: 协议族
AF_UNX / AF_LOCAL 用于本地通信
AF_INET 选择IPv4协议
AF_INET6 选择IPv6协议
type: 创建套接字类型
SOCK_STREAM 流式套接字对应TCP与SCTP协议、数据传输时逐字节传输
SOCK_DGRAM 数据报套接字对应UDP通信、数据传输时逐数据报传输(包含目的地址和其他数据)
SOCK_SEQPACKET 有序分组套接字对应SCTP协议
SOCK_RAM 原始套接字,跳过传输层
protocol:
0 表示默认方式
IPPROTO_TCP、IPPROTO_UDR、IPPROTO_SCTP
返回值:返回的是编号最低的文件描述符,失败返回-1,并置error
创建服务器:
1、创建套接字 socket()
2、绑定本机IP地址与端口号
int bin(int sockfd, const struct sockaddr *addr, socklen_taddrlen)
参数:
sockfd: 监听的套接字
addr: 对应协议服务器地址结构首地址:协议族选择、IP地址、port
addrlen:地址结构的大小
3、设置监听套接字
int listen(int sockfd, int backlog)
参数:
sockfd: 监听的套接字
backlog:等待连接的最大队列长度
返回值: 成功返回0,失败返回0,并置error
4、等待客户端连接
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
参数:
addr: 客户端的结构的首地址
addrlen: 客户端的结构的大小的首地址
注: 若不想接收客户端的信息,addr和addrlen设置为NULL;
返回值:成功返回套接字,失败返回-1,并置ERROR
字节序:
5、数据收发
read()/write()--recv()/send()
6、关闭套接字
close();
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#define BUF_N 1024
#define LISTEN_MAX 5
#define PORT 8888
#define SERVER_IP "192.168.113.129"
#define DEF_VAL 0
#define ERR_VAL -1
#define ERR_LOG(val) do{perror(val);exit(EXIT_FAILURE);}while(0);
int main(int argc, const char * argv[])
{
int ret = 0;
//创建套接字
int sockfd = socket(AF_INET, SOCK_STREAM, DEF_VAL);
if (ERR_VAL == sockfd)
{
ERR_LOG("socket");
}
printf("====create socket successful sockfd: %d\n",sockfd);
struct sockaddr_in saddr;
memset(&saddr, 0, sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(PORT);
saddr.sin_addr.s_addr = INADDR_ANY;
/*
//字符串形式转换为网络字节序
ret = inet_pton(AF_INET, SERVER_IP, &saddr.sin_addr);
if (ERR_VAL == ret)
{
ERR_LOG("inet_pton");
}
*/
//绑定本机地址与端口号
ret = bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr));
if (ERR_VAL == ret)
{
ERR_LOG("bind");
}
printf("====bind sucessful\n");
//设置监听套接字
ret = listen(sockfd, LISTEN_MAX); //将套接字转换为被动套接字
if (ERR_VAL == ret)
{
ERR_LOG("listen");
}
printf("====listen successful\n");
printf("wait client connect...\n");
//循环等待客户端连接
while(1)
{
struct sockaddr_in caddr;
memset(&caddr, 0, sizeof(caddr));
socklen_t caddr_len = sizeof(caddr);
//阻塞等待客户端连接、套接字必须为被动套接字,否则报错
int connfd = accept(sockfd, (struct sockaddr *)&caddr, &caddr_len);
if (ERR_VAL == connfd)
{
ERR_LOG("accept");
}
char dst[20] = {0};
printf("====accept successful %s : %d\n",
inet_ntop(AF_INET, &caddr.sin_addr, dst, sizeof(dst)), ntohs(caddr.sin_port));
char buf[BUF_N] = {0};
//数据发送与接收
while(1)
{
int len = read(connfd, buf, sizeof(buf));
if (len <= DEF_VAL)
{
printf("client exit...\n");
len = 1;
break;
}
write(connfd, buf, strlen(buf));
printf("client %s : %d send massage: %s",
dst, ntohs(caddr.sin_port), buf);
memset(buf, 0, sizeof(buf));
}
close(connfd);
}
close(sockfd);
return 0;
}
创建客户端:
1、创建套接字
2、绑定本机IP地址与端口号(可以省略,内核会做)
3、向服务器发起连接
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
参数:
addr:服务器地址结构的首地址
注:connect调用失败必须关闭套接字,不能重复使用
返回值: 成功返回0,失败返回-1,并置error
4、数据收发:
read()/write()--recv()/send()
5、关闭套接字
close()
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#define BUF_N 1024
#define DEF_VAL 0
#define ERR_VAL -1
#define ERR_LOG(val) do{perror(val);exit(EXIT_FAILURE);}while(0);
int main(int argc, const char * argv[])
{
int ret = 0;
if (argc != 3)
{
fprintf(stderr, "Usage %s filename\n", argv[0]);
exit(EXIT_FAILURE);
}
int port = atoi(argv[2]);
//创建套接字
int sockfd = socket(AF_INET, SOCK_STREAM, DEF_VAL);
if (ERR_VAL == sockfd)
{
ERR_LOG("socket");
}
printf("====create socket successful sockfd: %d\n",sockfd);
struct sockaddr_in saddr;
memset(&saddr, 0, sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(port);
ret = inet_pton(AF_INET, argv[1] ,&saddr.sin_addr);
if (ERR_VAL == ret)
{
ERR_LOG("inet_pton");
}
//向服务器发起连接
ret = connect(sockfd, (const struct sockaddr *) &saddr, sizeof(saddr));
if (ERR_VAL == ret)
{
close(sockfd);
ERR_LOG("connect");
}
printf("====connect successful\n");
char buf[BUF_N] = {0};
//数据发送接收
while(1)
{
int len = read(STDIN_FILENO, buf, BUF_N);
ret = send(sockfd, buf, len, 0);
if (ERR_VAL == ret)
{
ERR_LOG("send");
}
memset(buf, 0, sizeof(buf));
ret = recv(sockfd, buf, len, 0);
if (ERR_VAL == ret)
{
ERR_LOG("recv");
}
printf("from server massage: %s", buf);
memset(buf, 0, sizeof(buf));
}
close(sockfd);
return 0;
}
字节序
大端字节序:低数据存在高位(ARM)、网络字节序
小段字节序:低位数据存在低地址位(inter芯片)、本机字节序
大小端字节序在内存中存放数据有差异
代码判断大小端:
int a = 0x12345678;
short *p = (short *)&a;
if (*p == 0x78)
{
printf("小段\n");
}
else
{
printf("大端\n");
}
TCP通信特点:
1、点对点的连接(双方的套接字只能相互间接收和发送、双方具有固定的IP和port)
2、保证数据准确性、内容、时许无误
3、无丢失、无重复、无差错、按时到达(TCP出错机制保证)
TCP出错机制:TCP向另一端传送数据时,要求对面返回一个确认,如果没有确认,TCP就重传并等待更长时间,数次失败后放弃;
三次握手:
第一次:客户端应用程序主动开启,并向服务端发出请求报文段(握手钱服务器需处于LISTEN状态),(此后客户端处于SYN_SENT状态)
第二次:服务器应用进程被打开,发回确认报文,(此后服务器处于SYN_RCVD状态)
第三次:客户端接收到确认报文后,通知应用进程连接已建立,(客户端处于ESTABLISHEN状态)并向服务器发送报文,当服务器收到客户端的确认报文之后,也通知其上层应用进程连接已建立(服务器处于ESTABLISHED状态)、客户端在第三次握手时就可以向服务器发送数据了;
目的:确认客户端和服务器可以正常通信
四次挥手
由于TCP连接是全双工的,因此每个方向都必须单独进行关闭;
请求断开的可以是服务器也可以是客户端,但大多时候都是客户端;
客户端为主动方:
第一次:TCP客户端主动发送一个FIN,用来关闭客户到服务器的数据传送;
第二次:服务器收到这个FIN,它发回一个ACK,确认序号为收到的序号加1;
第三次:服务器关闭客户端的连接,发送一个FIN给客户端;
若服务器没有需要处理的数据,第二次和第三次可以合并为一次;
第四次:客户端发回ACK报文确认,并将确认序号设置为收到序号加1;