TCP的会产生三次握手和四次挥手,这在我的原来的博客中也描述过就不多描述了
TCP/UDP位于传输层
- 传输层:主要负责不同主机间进程之间的通信服务。
- 端口:计算机进程的标识序号,用于提供不同主机之间通信服务的标识
- 0 ~ 1023称为系统端口号
- 1024 ~ 49151称为用户端口号
- 19152 ~ 65535称为动态端口号
- TCP协议:在两台主机之间创建持久性的连接,提供可靠数据流传输,数据准确,多用于保证数据不丢失的场合,如发送邮件缺点是要建立连接效率没有udp高
- UDP协议:一个轻量级的协议,封装数据并将其从一台主机的一个端口发送到另一台主机的端口,优点效率快,多用于如视频传输之类不在乎一两个包的丢失的场合
- 缺点:不保证数据传输和准确到达。不可靠
相关API
TCP创建服务器的步骤
1创建Socket
windos平台必须初始化socket:
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
SOCKET socket(int af, int type, int protocol)
- 参数af指地址族:
- AF_NET:IPV4协议
- AF_NET6:IPV6协议
- TYPE指发送的数据类型
- SOCK_STREAM:有序可靠的数据段(TCP协议)
- SOCK_DGRAM:离散的报文(UDP协议)
- Protocol指协议类型
- IPPROTO_UDP:UDP协议
- IPPROTO_TCP:TCP协议
- 如果填0,根据前两个参数选取默认协议
- 参数af指地址族:
- 绑定SOCKET:
int
PASCAL FAR bind( SOCKET sockaddr,
const
struct
sockaddr FAR* my_addr,
int
addrlen);
//2.绑定端口 sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(5566); addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); //addr.sin_addr.S_un.S_addr = ADDR_ANY; //任何地址 int result = bind(socket_, (sockaddr*)&addr, sizeof(addr)); if (result == SOCKET_ERROR) { reportError("bind\n"); return; }
- Socket地址存储结构体
- struct sockaddr //标准库
{
uint16_t sa_family; //地址族
char sa_data[14]; //
}
struct sockaddr_in //微软提供
{
short sa_family; //指代协议族,在socket编程中只能是AF_INET
uint16_t sin_port; // 存储端口号(使用网络字节顺序)
struct in_addr sin_addr; //存储IP地址,使用in_addr这个数据结构
char sin_zero[8]; //是为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节
}
- 2设置并行最大监听数,监听的是客户端并发的最大数量
int result = listen(s, SOMAXCONN);//SOMAXCONN宏为默认最大监听数
if (result < 0)
{
reportError(_T("listen"));
return 0;
}
showMsg(_T("listen ok"));
- 3服务器接受客户端连接请求
sockaddr_in caddr;
int len = sizeof(addr);
int cs = accept(s, (sockaddr*)&caddr, &len); // s为请求连接客户端的socket
if (cs < 0)
{
reportError(_T("accept"));
break;
}
- 4接收消息
//注意send的第三个参数写自己要实际发送的长度,len大了的话会把多余的数据发送过去
int result = send(s, buffer, strlen(buffer) + 1, 0);
if (result < 0)
{
reportError(_T("send"));
return 0;
}
showMsg(_T("send ok"));
- 5发送消息
//注意协议接收到的数据可能大于buf的长度,所以在这种情况下要调用几次recv函数才能把s的接收缓冲中的
//数据copy完。recv函数仅仅是copy数据,真正的接收数据是协议来完成的
int result = recv(s, buffer, sizeof(buffer), 0);
if (result < 0)
{
reportError(_T("recv"));
return 0;
}
showMsg(_T("recv ok"));
- 通过客户端socket获取地址信息
int cs = (int)lpParameter; //客户端socket
sockaddr_in caddr;
int len = sizeof(caddr);
//客户端连接 通过socket获取地址信息
if (getpeername(cs, (sockaddr*)&caddr, &len) < 0)
{
reportError(_T("getpeername"));
return 0;
}
TCP创建客户端步骤
相关api
1创建socket
2连接connect
//连接
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(58888);
if (inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr) < 0)
{
ReportError("inet_pton");
return 0;
}
int nRet = connect(g_nSocket, (sockaddr*)&addr, sizeof(addr));
if (nRet < 0)
{
ReportError("connect");
return 0;
}
3发送 send
4接收recv
TCP粘包问题解决
粘包:因为TCP是字节流的传输方式,没有边界,所以在网络传输快的情况下,会把两个包粘成一个包发送,网络慢的话有可能只发送半个包,剩下的半个和下一个包一起发过来
一般解决方式:在包头加上一个包体的长度,这样的话无论是哪种情况,我只有读到这个长度才结束,这样收到的包就是完整的,也可以实现包的拆分,下一个包的位置就是首地址加上上一个包的长度就可以找到了
UPD创建服务器的步骤
1初始化后创建socket
2绑定bind
3发送sendto
CHAT_HEAD_INFO sendHead;
sendHead.version = 1;
sendHead.len = 0;
sendHead.cmd = CHAT_LOGIN_ERROR;
//发送
int result = sendto(socket_, (char*)&sendHead, sizeof(sendHead), 0, (sockaddr*)&caddr,
sizeof(caddr));
if (result <= 0)
{
reportError("sendto");
return -1;
}
4接收recvfrom
sockaddr_in caddr;
int len = sizeof(caddr);
CHAT_HEAD_INFO head;
int result;
char *buffer = NULL;
//接受消息
result = recvfrom(socket_, (char*)&head, sizeof(head), 0, (sockaddr*)&caddr, &len);
if (result <= 0)
{
reportError("recvfrom\n");
return 0;
}
UPD创建客户端的步骤
1初始化后创建socket
2发送接收sendto recvfrom
数据传输自定义的协议分为二进制协议和文件协议
文本协议
- 优势
- 协议自由,方便修改
- 劣势
例子如下
- 解析数据过程占用时间过长
使用特定分隔符号标识每一行数据
// command:sayto \n
// lenth : 6 \n
// source id : 100 \n
// dest id : 200\n
// msg : hello
//按对应格式,格式化字符串后打包整个数据并输出
sprintf_s(szMsg, sizeof(buffer),
"cmd:sayto\n"
"sourceid:%d\n"
"deskid:%d\n"
"len:%d\n"
"msg:"
"%s", nSourceID, nDeskID, nlength, szBuff);
int result = send(cinfo.ucs, szMsg, strlen(szMsg) + 1, 0);
二进制协议
- 优势
- 传输解析速度快
- 劣势
例子如下
- 扩展性不好
例子
enum CHAT_COMMAND
{
CHAT_LOGIN, //登录
CHAT_ACK, //确认
CHAT_LOGIN_OK, //成功
CHAT_LOGIN_ERROR, //失败
CHAT_ALL_USER, //通知所有用户
CHAT_LOGOUT, //登录
CHAT_SAY_ALL, //对所有人说
CHAT_SAY_TO, //对人说
CHAT_HEARTBEAT, //心跳包
};
struct CHAT_HEAD_INFO
{
int version;
int cmd;
int len;
};
CHAT_HEAD_INFO sendHead;
sendHead.version = 1;
sendHead.len = 0;
sendHead.cmd = CHAT_LOGIN_ERROR;
int result;
result = send(nScoket,(char*)&sendHead, sizeof(sendHead), 0);
if (result <= 0)
{
reportError("send");
return -1;
}