TCP编程流程
socket()创建套接字,套接字TCP协议选择流式服务SOCK_STREAM。
//返回套接字文件描述符
int socket
(
int domain, //地址族规范(ipv4还是ipv6)
int type,//新套接字的类型规范(TCP还是UDP)
int protocol//要使用的协议(默认给0)
);
bind()指定套接字使用的IP地址和端口。IP地址是自己主机地址,端口为一个16位的整形值。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
//绑定成功返回0,失败返回-1
//(套接字描述符,地址结构,套接字地址长度);
listen()方法创建监听队列。监听队列分为存放未完成三次握手的连接和完成三次握手的连接。其第二个参数位指定已完成三次握手队列的长度。
int listen(int sockfd, int backlog);
//成功返回0,失败返回-1
//(被监听的套接字,完全连接状态套接字上限)
accept()处理存放在listen创建的已完成三次握手的队列中的连接,如果队列为空可能阻塞。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
//(执行过listen系统调用的监听套接字,被连接的远端套接字地址,套接字地址长度)
connect()有客户端程序执行建立连接,进行三次握手,指定连接的服务器IP地址和端口。
int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);
//(套接字返回的值,服务器监听的套接字的地址,这个地址的长度)
send()向TCP连接的对端发送数据。
recv()用接收TCP连接的对端发送来的数据,返回值为0说明对方已经关闭连接。
ssize_t recv(int sockfd, void *buff, size_t len, int flags);
ssize_t send(int sockfd, const void *buff, size_t len, int flags);
close()关闭TCP连接。
int close(int sockfd);
TCP有关概念
TCP协议:面向连接的可靠的流式服务。(传输层)
TCP三次握手建立连接,四次挥手断开连接。
挥手也可以只执行三次(刚好服务端和客户端都要关闭,ACK和FIN一起发送)
TCP 的可靠传输是通过使用应答确认和超时重传来完成
可靠性:牺牲一定开销,应答确认、超时重传、去重(序号相同的丢弃)、乱序重排。
滑动窗口:流量控制(控制发送的数据量,太慢效率低,太快缓冲区满)
粘包:连续send()时,recv()的次数比send()少,无法准确分割出发送方要表达的意义。
解决:告诉大小,设置特殊符号分割,不连续send
字节序列:大端、小端
大端:网络字节序列(整形必须转成大端)
#include <netinet/in.h>
uint32_t htonl(uint32_t hostlong); // 长整型的主机字节序转网络字节序
uint32_t ntohl(uint32_t netlong); // 长整型的网络字节序转主机字节序
uint16_t htons(uint16_t hostshort); // 短整形的主机字节序转网络字节序
uint16_t ntohs(uint16_t netshort); // 短整型的网络字节序转主机字节序
TCP服务端代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
int main()
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);//创建套接字
if ( sockfd == -1)//创建失败退出
{
exit(1);
}
struct sockaddr_in saddr,caddr;//服务器地址、客户端地址
memset(&saddr,0,sizeof(saddr));//清空服务器地址
saddr.sin_family = AF_INET;//地址簇(使用ipv4)
saddr.sin_port = htons(6000);
//htons 将主机字节序转换为网络字节序: 1024 知名端口 , 4096保留, 临时端口
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");//回环地址
int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));//绑定端口、ip地址
if ( res == -1)
{
printf("bind err\n");
exit(1);
}
res = listen(sockfd,5);//创建监听队列
if ( res == -1)
{
exit(1);
}
while( 1 )//服务器循环接收客户端连接
{
int len = sizeof(caddr);
int c = accept(sockfd,(struct sockaddr*)&caddr,&len);//阻塞
if ( c < 0 )//没有接收到,继续接收
{
continue;
}
printf("c=%d\n",c);//接收到输出
char data[128]={0};
int n = recv(c,data,127,0);//阻塞
printf("n=%d,buff = %s\n",n,data);
send(c,"ok",2,0);//给客户端发送ok
close(c);
}
close(sockfd);
exit(0);
}
TCP客户端代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main()
{
// 创建套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0); // SOCK_STREAM是tcp的
if (sockfd == -1)
exit(1);
struct sockaddr_in saddr; // 服务器地址
memset(&saddr, 0, sizeof(saddr)); // 清空,(有一个占位的)
saddr.sin_family = AF_INET; // 地址族,告诉它用的什么协议ipv4,ipv6
saddr.sin_port = htons(6000); // 端口,进程代号,htons短整形的主机字节序转网络字节序
// 设置服务器地址
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");//哪个主机
// 连接服务器
int res = connect(sockfd, (struct sockaddr *)&saddr, sizeof(saddr));
if (res == -1)
{
printf("connect err\n");
exit(1);
}
while (1)
{
printf("input:\n");
char buff[128] = {0};
fgets(buff, 128, stdin);
if (strncmp(buff, "end", 3) == 0)
break;
send(sockfd, buff, strlen(buff), 0);//发送到发送缓冲区
memset(buff, 0, sizeof(buff)); // 清空
//接受数据
recv(sockfd, buff, 127, 0);//看接收缓冲区(全双工)
printf("buff = %s\n", buff);//输出接收到的数据
}
close(sockfd);//关闭套接字
exit(0);
}