学习socket的时候看的是Linux的教程,在Windows中写,发现有一些不一样,比如在关闭socket的时候用“close()”
,运行的时候就会弹出如下错误,后来发现要用“closesocket()”
。
还有Linux中的"fork()"
复制并开启进程,而Windows中则需要“HANDLE threadaccept = CreateThread(NULL, 0, do_service, &connSock, 0, NULL);”
。要有读取和发送信息等等。
为此,我找到了如下文章,放于此处,以便日后查阅。
从现在开始,程序要分为windows版和linux版,linux版就是加载一堆头文件,windows呢就是加载dll后声明一些函数,我们一点点开始,先看windows,想直接看linux的可以跳过windows
windows:
#include <stdio.h>
#include <winsock2.h>//socket头文件
#pragma comment (lib,"ws2_32.lib")//加载socket
int main()
{
WSADATA wsaData;//生成句柄
WSAStartup(MAKEWORD(2, 2), &wsaData);//初始化
/*codding*/
WSACleanup();//结束调用
return 0;
}
这就是windows的大体结构,ws2_32.lib用的是编译时加载,具体的加载方式和显示加载还是隐式加载大家可以自行查阅,这里不做深究,下面对函数进行分析:
首先看一下WSADATA结构体:
typedef struct WSAData {
WORD wVersion;
WORD wHighVersion;
char szDescription[WSADESCRIPTION_LEN+1];
char szSystemStatus[WSASYS_STATUS_LEN+1];
unsigned short iMaxSockets;
unsigned short iMaxUdpDg;
char FAR *lpVendorInfo;
} WSADATA, *LPWSADATA;
具体每个变量是做什么的就要大废周章了,大家可以 点击这里查看微软官方的解释,大概意思就是存放期望调用方使用的Windows套接字规范的版本
再看一下WSAStartup()函数,原型:
int WSAStartup(
_In_ WORD wVersionRequested,
_Out_ LPWSADATA lpWSAData
);
第二个参数lpWSADATA就是WSADATA结构体,第一个WORD参数,用的MAKEWORD()函数,原型如下:
WORD MAKEWORD(
BYTE bLow,
BYTE bHigh
);
这个函数主要就是返回一个WORD参数。
接下来看看WSAClearup()函数:
int WSACleanup(void);
官方解释是:The WSACleanup function terminates use of the Winsock 2 DLL(Ws2_32.dll).意思就是不再使用Ws2_32.dll库了。
到此,socket库的调用就说完了,具体还有很多很多没有讲,读者可以自查资料深入研究,最后一节我会给使用到的链接,目前就会使用就行。
Linux:
linux下比较易于理解,主要就是包含一堆头文件就行,demo如下:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main()
{
/*codding*/
}
前三个是C++基础文件,不做赘述,第四个unisted.h是linux下特有的头文件,主要包含了read,write等函数,后三个主要是socket需要的库文件。
前面讲了一大推分量很小的东西,现在才开始真正的socket编程,这里要明白一个概念,什么是句柄,这个概念在本案例中可以初步认为就是一个int(显然这是错误的,目前先这么理解,因为在linux中并没有句柄)。首先需要调用socket函数,分为windows和linux版本
windows:
SOCKET sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
Linux:
int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
其中SOCKET指的就是句柄,而在linux中用int代替。
现在已经创建一个套接字,供后面的程序使用,记住一个连接用一个套接字,什么意思,就是假如你的机器A和另一个机器B需要TCP连接,这时需要创建一个套接字,然后你的机器A和机器B需要另一个UDP连接,这时候你就需要再创建一个套接字。
win和linux用法是一样的,我们来看一下他的原型:
SOCKET WSAAPI socket(
_In_ int af,
_In_ int type,
_In_ int protocol
);
下面看一个每个值得取值,这里仅仅列出常见取值,其他取值有兴趣可以参考这个
af:协议族
type:套接字的类型
protool:传输协议(要和协议和套接字配套,本例指的是TCP或UDP)
好了,现在已经创建了套接字, 接下来就要给套接字绑定ip和端口了,查看bind()函数:
值得一提的是,bind函数一般用在服务器上,也就是服务端,稍后会讲解客户端需要用什么函数,先看一下bind使用:
windows:
bind(servSock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));//Windows
== Linux:==
bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
以上都是实例,参数可能不是很准确,所以看一下函数原型不就知道该怎么写了嘛
int bind(
_In_ SOCKET s,
_In_ const struct sockaddr *name,
_In_ int namelen
);
其中,第一个参数s指的就是一开始创建的套接字,不在赘述,这里主要讲一下后面两个参数:
第二个参数是要传入一个sockaddr型的结构体,而这个结构体长啥样呢,我们来看一下
struct sockaddr{
sa_family_t sin_family; //地址族(Address Family),也就是地址类型
char sa_data[14]; //IP地址和端口号
};
第一个参数还好说,协议族,和创建socket的第一个参数一样,俺么第二个参数呢,14位的地址和端口号应该怎么填?
这时候,我们就要借助另外一个结构体sockaddr_in(至于为什么要借助另外一个结构体,这里涉及到历史遗留问题,感兴趣可以自行查阅)
struct sockaddr_in{
sa_family_t sin_family; //地址族(Address Family),也就是地址类型
uint16_t sin_port; //16位的端口号
struct in_addr sin_addr; //32位IP地址
char sin_zero[8]; //不使用,一般用0填充
};
这里把每个变量位数已经标好了,可以看到位数和上一个结构体一样,所以这里我们用sockaddr_in结构体定义好后替换成sockaddr结构体,都坐下,正常操作是:
sockaddr_in sockAddr;
memset(&sockAddr, 0, sizeof(sockAddr));//各位清零
sockAddr.sin_family = PF_INET;//协议族
sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");//ip地址
sockAddr.sin_port = htons(1996);//端口
bind(servSock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));//Windows
首先创建一个sockaddr结构体,将各位清零(防止乱码),然后设置协议族,ip地址,端口等,最后将这些信息绑定到最开始创建的句柄上,这样一个套接字就完成了绑定
这里介绍一下inet_addr函数,这个函数是将一个ip字符串转换成unsigned long,同样也是历史因素,想具体了解可以查看这里,不过在windows下vs建议换成inetPton(),不过我就是任性啊,就不换,嘻嘻,在linux下这个函数可不可以替换我没有验证,如果有人验证了可以在这篇文章下面留言
htons函数返回一个网络字节数,看一下官方解释,还有htonf()htonl()htonll()等函数,可以好好研究一下
connent函数和bind函数用法一样,只不过connent是客户端的程序,我给列一下函数原型,用法可以在下篇看到
int connect(
_In_ SOCKET s,
_In_ const struct sockaddr *name,
_In_ int namelen
);
函数原型,参数和bind一样,所以用法也一样,不多说。
listen函数需要在connect之前,在bind函数之后的服务器端的函数,看名字就知道是用来监听的函数,用法:
Windows/Linux:
listen(servSock, 20);
原型是:
int listen(
_In_ SOCKET s,
_In_ int backlog
);
比较容易理解,第一个参数就是bind后的套接字,而第二个参数指的是等待连接队列的最大长度
,啥意思?
就是说socket有2个缓冲区,一个是发送缓冲区,一个是接受缓冲区,他们有固定的大小,超过了这个大小,数据不就接收不到了嘛,这对文件传输可以一个麻烦,所以这个等待连接队列指的是可以等待多少个缓冲区大小的数据,这里了解到这里就可以了,具体应用可以查看后面的文件传输内容在深入研究
参考连接:
【1】https://www.cnblogs.com/elve960520/articles/8361614.html
【2】http://c.biancheng.net/cpp/html/3032.html
【3】http://blog.csdn.net/y396397735/article/details/50655363
【4】http://blog.csdn.net/tennysonsky/article/details/45621341
【5】http://msdn.microsoft.com