[学习]Socket套接字

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
closesocket(sock);

下面把代码组合成一个具备基础信息收发功能的程序。

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[])
{
//初始化DLL
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)); //每个字节都用0填充
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;
// _sleep(50);
cout << "input your massage: " << endl;
fgets(szBuffer, 512, stdin);
if(scmp(szBuffer,"exit") ){ //输入exit关闭连接
break;
}
cout << "-------------------------------" << endl;
cout << endl;
send(sock, szBuffer, 512, 0);
}


//关闭套接字
closesocket(sock);
//终止使用 DLL
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;
// #include <sys/socket.h>
// #include <netinet/in.h>
// #include <arpa/inet.h>

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下套接字使用的学习。


[学习]Socket套接字
http://example.com/2021/03/22/学习/学习-socket/
Author
peach-water
Posted on
March 22, 2021
Licensed under