socket套接字学习
一、前言
计算机网络课程学习使用操作系统库socket库实现一个能够在局域网内收发消息的命令行程序。参考资料来主要自于网上的库用法教程,留下此文记录下学习的过程。
二、介绍
socket套接字是计算机网络中应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实是一个API将复杂的TCP/IP协议隐藏在socket接口后面,对于使用的用户来说,一组简单的接口就是全部,让Socket去组织数据,而用户自己只需要关心好传输的数据,对于怎么传输并不需要过多了解。
网络协议有分TCP和UDP两种协议,他们的作用都是收发数据包。唯一的不同是TCP面向连接,在收发数据包之前还有一个连接过程,只有建立连接之后才能发送数据包。而UDP并不需要这样一个过程,它可以做到只给一个地址,马上就能够发送,有急速发货那味了。
他们之间各有优劣。
| 协议类型 |
优点 |
缺点 |
主要用途 |
| UDP |
可以很快发送数据 |
容易丢包,易受到网络波动影响 |
视频通话等对丢包不敏感,但是对延迟敏感的领域 |
| TCP |
稳定,不会丢包 |
发送数据慢,但是不会受到网络波动影响 |
文件传输,聊天等对丢包敏感的领域 |
三、使用
1、TCP客户端
以下教程基于Windows10环境。
首先加入socket头文件库。
1 2 3
| #include <Winsock2.h> #include <sindows.h> #pragma comment(lib, "WS2_32.lib")
|
在使用之前还需要初始化,按照以下的方式初始化链接库,在具体收发数据前调用。
1 2 3 4 5 6 7 8
| int main(){ WSADATA wsd; if (WSAStartup(MAKEWORD(2, 2), &wsd)) { printf("WSAStartup failed!\n"); return 1; } }
|
在一切准备妥当之后,建立第一个套接字。
1 2 3 4 5
| SOCKET sock; SOCKET sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); \\ PF_INET指定连接协议族、SOCK_STREAM表明是一个TCP协议 addr.sin_family = AF_INET; \\ addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); \\指定服务器IP4地址 addr.sin_port = htons(1234); \\服务器开放的监听端口
|
然后向服务器发送连接建立请求。
1
| connect(sock, (SOCKADDR *)&sockAddr, sizeof(SOCKADDR)+10);
|
成功建立连接后,可以使用send函数向服务器发送数据了。
1
| send(sock, "aaa", 512, 0); \\sock表明地址,“aaa”是发送的数据,512是发送字符数量(当然可以使用sizeof动态,但是这里就偷懒拉),0是一个标识符,一般不动
|
使用recv函数接收来自服务器的消息。
1
| recv(sock, szBuffer, 512, 0); \\szBuffer是接受字缓冲区(就是收到的字符写到哪里),
|
使用完连接资源后应当及时调用套接字关闭函数,释放被占用的资源。
下面把代码组合成一个具备基础信息收发功能的程序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| #include <iostream> #include <stdlib.h> #pragma comment(lib, "ws2_32.lib") #include <WinSock2.h> using namespace std;
bool scmp(char *a, char *b){ if(a[0] == b[0] && a[1] == b[1] && a[2] == b[2] && a[3] == b[3]){ return true; } return false; }
int main(int argc, char *argv[]) { WSADATA wsaData; if (WSAStartup(MAKEWORD(2, 2), &wsd)) { printf("WSAStartup failed!\n"); return 1; } SOCKET sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); sockaddr_in sockAddr;
memset(&sockAddr, 0, sizeof(sockAddr)); sockAddr.sin_family = PF_INET; sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); sockAddr.sin_port = htons(1234);
if (connect(sock, (SOCKADDR *)&sockAddr, sizeof(SOCKADDR)+10)) { cout << "client connect error" << endl; closesocket(sock); WSACleanup(); system("pause"); return 1; } send(sock, "aaa", 512, 0); while (true) { char szBuffer[MAXBYTE] = {0}; recv(sock, szBuffer, MAXBYTE, 0); cout << "Message from server: \n" << szBuffer << endl <<"------------------------------"<< endl; cout << "input your massage: " << endl; fgets(szBuffer, 512, stdin); if(scmp(szBuffer,"exit") ){ break; } cout << "-------------------------------" << endl; cout << endl; send(sock, szBuffer, 512, 0); }
closesocket(sock); WSACleanup(); system("pause"); return 0; }
|
2、TCP服务器
上面代码已经建立了一个具备信息收发能力的客户端,接下去写一个能够接受信息也可以发送信息进行交互的服务端程序。这个服务端程序的功能很简单,启动后接收来自客户端的socket连接,其余部分照抄来自客户端的部分。
服务端开头于客户端相同,不同的是套接字的建立方式。
1 2 3 4 5
| sockaddr_in sockAddr; sockAddr.sin_family = PF_INET; sockAddr.sin_addr.s_addr = htonl(INADDR_ANY); \\表示绑定本机IP4地址 sockAddr.sin_port = htons(1234); bind(Sock, (SOCKADDR *)&Addr, sizeof(SOCKADDR)); \\把这个套接字绑定到服务器对应端口
|
接收来自客户端的连接。
1
| SOCKET clntSock = accept(Sock, (SOCKADDR *)&Addr, &nSize);
|
完整的服务端代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
| #include <iostream> #include <stdlib.h> #include <sys/types.h> #pragma comment(lib, "ws2_32.lib") #include <WinSock2.h> #include <windows.h>
using namespace std;
int main(int argc, char *argv[]) { WSADATA wsaData; WSAStartup(MAKEWORD(2, 2), &wsaData); SOCKET Sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); sockaddr_in sockAddr; memset(&sockAddr, 0, sizeof(sockAddr)); sockAddr.sin_family = PF_INET; sockAddr.sin_addr.s_addr = htonl(INADDR_ANY); sockAddr.sin_port = htons(1234); bind(Sock, (SOCKADDR *)&sockAddr, sizeof(SOCKADDR));
if (listen(Sock, SOMAXCONN) < 0) { cout << "listen set error" << endl; system("pause"); return 1; }
SOCKADDR clntAddr; int nSize = sizeof(SOCKADDR); SOCKET clntSock = accept(Sock, (SOCKADDR *)&clntAddr, &nSize); \\clntSock是收到的来自客户端地址,十分重要是之后发送数据依据 if (clntSock == SOCKET_ERROR) { cout << "error" << endl; closesocket(clntSock); closesocket(Sock); WSACleanup(); system("pause"); return 1; } else { cout << "OK to connect" << endl; }
char recv_buf[100]; int recv_len = 0;
while (1) { recv_len = recv(clntSock, recv_buf, 100, 0); if (recv_len < 0) { cout << "receive error !!" << endl; break; } else { cout << "message received" << recv_buf << endl; } cout << "send out your message" << endl; char send_buf[100]; cin >> send_buf; int send_len = strlen(send_buf); send_len = send(clntSock, send_buf, 100, 0); if (send_len < 0) { cout << "send out successfully " << endl; break; } } closesocket(clntSock); closesocket(servSock); WSACleanup(); system("pause"); return 0; }
|
四、总结
总体来说实验还是非常轻松的,可以将服务端放到有公网IP的服务器上(完全没有安全措施就是了)。实现了一个非常简易的聊天室,尽管目前代码十分不完善,比如说只能在发送消息之后才能显示来自服务端的消息。
本文是windows下套接字使用的学习。