注:本章内容大部分为第一章的服务器与客户端代码的解析与理解,十分重要
协议
服务器端和客户端为了能进行数据交换,他们必须遵循同一个协议:
创建套接字
int socket(int domain, int type, int protocol)//Linux
Socket socket(int domain, int type, int protocol)//Win
domain 参数为协议族(Protocol Family)信息
type 为套接字数据传输类型信息
protocol 为计算机通信中使用的协议信息
如:
hServerSock = socket(PF_INET, SOCK_STREAM, 0);
代码来自第一章内容中套接字的创建,其中,PF_INET,为IPv4互联网协议族。
协议族(Protocol Family)
Linux中,头文件sys/socket.h中声明的协议族(Windows在<WinSock2.h>中):
PF_INET | IPv4协议族 |
PF_INET6 | IPv6协议族 |
PF_LOCAL | 本地通信的UNIX协议族 |
PF_PACKET | 底层套接字的协议族 |
PF_IPX | IPX Novell协议族 |
业界常用PF_INET对应的IPv4协议族,且《TCP/IP网络编程》主要将重点放在这PF_INET上。
套接字类型(type)
socket()中第二个参数为套接字类型,指的是套接字的数据的传输方式。
决定套接字的传递方式的原因:每一个协议族内会存在不同的传输方式。
两种代表性的传输方式:
1.面向连接的套接字 SOCK_STREAM
hServerSock = socket(PF_INET, SOCK_STREAM, 0);
- 传输过程数据不会消失
- 按顺序传输数据
- 传输的数据不存在数据边界(因为不存在数据边界,所以要注意要在缓冲区(buffer)满之前将数据读出,也就是缓冲区写入速度要小与读出速度)
2.面向消息的套接字 SOCK_DGRAM
- 强调快速的传输而非传输顺序
- 传输的数据可能丢失/损毁
- 有数据边界
- 限制每次传输的数据大小
这种传输方式与SOCK_STREAM相比突出的是传输的速度这一特性,但他却无法避免数据丢失、损毁
注:面向消息的套接字不存在连接的概念
协议的最终选择(socket()第三个参数)
socket()内前两个参数满足了大部分情况,我们向他传递0,但如果协议族中存在多个 数据方式传输方式相同的协议时,传输方式相同,但协议不同,这时就需要第三个参数具体指定协议信息。
int tcp_sock= socket(PF_INET, SOCK_STREAM, IPPROTO_UDP);//Linux
这里前两个参数指定了IPv4协议族,SOCK_STREAM是面向连接的数据传输:因此满足这两个条件的协议只有IPPROTO_UDP。
基于WINDOWS的TCP套接字示例
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include<iostream>
#include<WinSock2.h>
#pragma comment(lib,"ws2_32.lib")
//这是将静态库连接到工程中
//否则会出现一串无法解析的外部符号的LINK2019的报错
void ErrorHandling(const char* message);
int main()
{
WSADATA wsaData;
//声明SOCKET变量保存socket()函数的返回值
//在Windows里sokect()的返回值是SOCKET
SOCKET hSocket;
SOCKADDR_IN servAddr;
char message[30];
int strLen = 0;
int readLen = 0;
int idx=0;
/*
if (argc != 3) {
printf("Usage: %s <IP> <port>\n", argv[0]);
exit(1);
}*/
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
ErrorHandling("WSAStartup() error!");
hSocket = socket(PF_INET, SOCK_STREAM, 0);
if (hSocket == INVALID_SOCKET)
ErrorHandling("socket() error!");
memset(&servAddr, 0, sizeof(servAddr));
servAddr.sin_family = AF_INET;
servAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
servAddr.sin_port = htons(8888);
if (connect(hSocket, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)
ErrorHandling("connect() error!");
//while中recv()读取数据,每次1字节
//第一章中:
//strlen = recv(hSocket, message, sizeof(message) - 1, 0);
//每次读取的字符数是整个message的sizeof(message);
while (readLen = recv(hSocket, &message[idx++], 1, 0))
{
if(readLen==-1)
ErrorHandling("read() error!");
strLen += readLen;
if (message[idx - 1] == '\0')//字符串结束符跳出循环
break;
}
std::cout << "Message from server : " << message << std::endl;
std::cout << "Function read call count : " << strLen << std::endl;
closesocket(hSocket);
WSACleanup();
getchar();
return 0;
}
void ErrorHandling(const char * message) {
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
注:
#define _WINSOCK_DEPRECATED_NO_WARNINGS
是为了防止安全检查,当然你也可以根据编译器的报错选用他推荐的函数。
在添加了:
#include<WinSock2.h>
之后:
#pragma comment(lib,"ws2_32.lib")
这是将静态库连接到工程中
否则会出现一串无法解析的外部符号的LINK2019的报错