本次作业主要是使用自己熟悉的语言完成一个简单的socket编程,并对比该语言的socket api和linux api之间的异同。因此我将先附上socket程序的代码,并分析socket api,与linux api加以比较
我使用的是winsock,C++语言,基于流套接字(TCP)。
server端:
#include#include<string> #include #include #include<string> #include #pragma comment(lib,"WS2_32.lib") using namespace std; static WSADATA wsaData; //wsadata结构包含有关windows套接字实现的信息 static SOCKET serversocket; //服务器socket static SOCKET clientsocket; //客户端socket static sockaddr_in sockin; //保存地址信息 int len = sizeof(SOCKADDR); //地址长度 char text[100]; //接收消息缓冲区 struct sockaddr_in sa; //客户端地址信息 int len_new = sizeof(sa); //地址长度 //创建并初始化套接字,包括填充地址信息 void init() { WORD sockVersion = MAKEWORD(2, 0); // 版本2.2 ::WSAStartup(sockVersion, &wsaData); //对winsock服务初始化 serversocket = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //创建套接字 //填充sockaddr_in结构 sockin.sin_family = AF_INET; //指定ipv4 sockin.sin_port = htons(7777); //绑定到7777端口;htons将端口号由主机字节序转换为网络字节序的整数值(host to net) sockin.sin_addr.S_un.S_addr = INADDR_ANY; //本地所有地址 } //清空缓冲区 void cleartext() { for (int i = 0; i < 100; i++) { text[i] = '\0'; } } int main() { char name[100]; init(); gethostname(name, 100); cout << "—————— server " << name << " is ready ————————" << endl; ::bind(serversocket, (sockaddr*)&sockin, sizeof(SOCKADDR)); //绑定到一个本地地址 if (listen(serversocket, 5) < 0) cout << "listen fail..." << endl; //开始监听,服务器同时能监听5个客户端socket clientsocket = ::accept(serversocket, (SOCKADDR*)&sa, &len); while (1) { int nRecv = ::recv(clientsocket, text, 100, 0); cout << "client says: "< endl; cleartext(); //清空缓冲区 string output = "got it"; ::send(clientsocket, output.c_str(), output.length(), 0); } closesocket(serversocket); WSACleanup(); system("pause"); }
client端:
#include#include #include #include<string> #pragma comment(lib,"WS2_32.lib") #define _WINSOCK_DEPRECATED_NO_WARNINGS using namespace std; WSADATA wsaData2; //服务器socket SOCKET Serversocket; //客户端socket SOCKET client; sockaddr_in sockin; static WSADATA wsaData; static SOCKET clientsocket; static sockaddr_in servAddr; char text[100]; void clear_text() { for (int i = 0; i < 100; i++) { text[i] = '\0'; } } void init() { WORD sockVersion = MAKEWORD(2, 0); ::WSAStartup(sockVersion, &wsaData); clientsocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // 填写远程地址信息 servAddr.sin_family = AF_INET; servAddr.sin_port = htons(7777); servAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); } int main() { cout << "—————— client is ready ————————" << endl; init(); if (connect(clientsocket, (sockaddr*)&servAddr, sizeof(servAddr)) == -1) cout << "connect fail" << endl; else { while (1) { string input; cin >> input; if (::send(clientsocket, input.c_str(), input.length(), 0) == -1) cout << "send wrong" << endl; clear_text(); ::recv(clientsocket, text, 100, 0); cout << "server says: " << text << endl; } } closesocket(clientsocket); WSACleanup(); system("pause"); }
运行结果:
我们将以上代码通过抽象成流程图的方式简要地看看整个过程中调用了哪些winsock的api。
我们来逐个分析一下这些函数调用,其中init()函数是我自己对winsock下socket创建和一些初始化工作的封装,其余调用均为winsock提供的api,我们依次来看,并对比其与linux api:
1.init(),完成相关初始化工作
winsock下:
//winsock WSADATA wsaData2; //服务器socket SOCKET Serversocket; //客户端socket SOCKET client; sockaddr_in sockin; static WSADATA wsaData; static SOCKET clientsocket; static sockaddr_in servAddr; void init() { WORD sockVersion = MAKEWORD(2, 0); ::WSAStartup(sockVersion, &wsaData); clientsocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // 填写远程地址信息 servAddr.sin_family = AF_INET; servAddr.sin_port = htons(7777); servAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); }
linux socket下:
//linux socket
struct sockaddr_in my_addr; //服务器网络地址结构体 struct sockaddr_in remote_addr; //客户端网络地址结构体 int sin_size; char buf[BUFSIZ]; //数据传送的缓冲区 memset(&my_addr,0,sizeof(my_addr)); //数据初始化--清零 my_addr.sin_family=AF_INET; //设置为IP通信 my_addr.sin_addr.s_addr=INADDR_ANY;//服务器IP地址--允许连接到所有本地地址上 my_addr.sin_port=htons(8000); //服务器端口号
对比winsock和linux socket我们发现,二者初始化工作大致的流程是相似的——主要都是完成对一个sockaddr_in结构体成员数据的填充,其中包括设置网络协议,绑定端口号等等,且sockaddr_in结构体的定义都是一样的。然后分别对server端和client端的socket初始化。
winsock下:
//winsock下 typedef struct sockaddr_in { ADDRESS_FAMILY sin_family; USHORT sin_port; IN_ADDR sin_addr; CHAR sin_zero[8]; } SOCKADDR_IN, *PSOCKADDR_IN;
linux socket下:
//linux socket下 struct sockaddr_in { short int sin_family; /* Address family */ unsigned short int sin_port; /* Port number */ struct in_addr sin_addr; /* Internet address */ unsigned char sin_zero[8]; /* Same size as struct sockaddr */ };
(可以观察到,winsock和linux socket对sockaddr_in的定义是一致的)
不同的是:
1.linux下的socket是基于文件的,serversocket和clientsocket本质上是一个文件描述符。而winsock下的socket是自己定义、封装的SOCKET类。
2.由于Winsock服务是以动态链接库的形式实现的,所以在使用前必须调用WSAStartup函数对其进行初始化,协商Winsock的版本支持,并分配必要的资源。并且要用#pragma comment(lib,"Ws2_32")来告知编译器链接该lib
2.bind()
winsock下:
//winsock ::bind(serversocket, (sockaddr*)&sockin, sizeof(SOCKADDR));
linux socket下:
//linux socket bind(server_sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))
对比winsock和linux socket下的bind函数,我们发现二者基本上是相同的:
winsocks下的bind:第一个参数serversocket指定一个未绑定的套接字句柄,用于等待客户进程的连接。 第二个参数指向sockaddr结构对象的指针。 第三个参数指定sockaddr结构的长度。
linux socket下的bind:第一个参数sockfd是由socket()调用返回的套接口文件描述符。第二个参数my_addr是指向数据结构sockaddr的指针。数据结构sockaddr中包括了关于你的地址、端口和IP地址的信息。第三个参数addrlen可以设置成sizeof(structsockaddr),也是结构体的长度。
3.listen()
winsock下:
//winsock listen(serversocket, 5)
linux socket下:
//linux socket listen(server_sockfd,5);
对比winsock和linux socket下的listen函数,我们发现二者基本上是相同的,不同的地方还是我们之前提到过的socket变量本身的区别——即一个是文件描述符,一个是winsock自己封装的SOCKET对象。
4.accept()
winsock下:
//winsock clientsocket = ::accept(serversocket, (SOCKADDR*)&sa, &len);
linux socket下:
//linux socket client_sockfd=accept(server_sockfd,(struct sockaddr *)&remote_addr,&sin_size)
对比winsock和linux socket下的accept函数,我们发现二者也基本上是相同的,对比和listen函数相同,不做赘述。
5.connect()
winsock下:
//winsock connect(clientsocket, (sockaddr*)&servAddr, sizeof(servAddr))
linux socket下:
//linux socket connect(client_sockfd,(struct sockaddr *)&remote_addr,sizeof(struct sockaddr))
同上面的listen和accept,不做赘述
6.send()
winsock下:
//winsock ::send(clientsocket, input.c_str(), input.length(), 0)
linux socket下:
//linux socket send(client_sockfd,buf,strlen(buf),0)
对比winsock和linux socket下的send(),我们发现,二者定义的send函数的参数列表是相同的,第一个参数为接收方socket,第二个参数为发送的内容,第三个为发送数据的长度,第四个为标志,一般为0
区别仅在于,由于对winsock的使用,我是基于C++的,所以我直接用了标准库里的string类型,在涉及到长度时直接调用了它的length()方法。
7.recv()
winsock下:
//winsock ::recv(clientsocket, text, 100, 0);
linux socket下:
//linux socket recv(client_sockfd,buf,BUFSIZ,0);
与send的对比相同。
8.close()
winsock下:
//winsock closesocket(clientsocket);
linux socket下:
//linux socket close(client_sockfd)
对比close(),winsock下是closesocket,而linux下是close()
综合以上,我们可以知道,分别基于两种操作系统的socket api:winsock和linux socke是完全独立的。但是,几个基本api所实现的功能、函数名、形参列表都是几乎相同的,虽然是完全独立的两套api,但书写出来的代码结构基本是相同的。