初探 Socket
本文最后更新于:2 年前
起因是寒假没事干搞了个70块一年的云服务器玩玩,试图整一个联机游戏demo
感谢大佬:https://www.cnblogs.com/dolphinX/p/3460545.html
socket是什么
这里以TCP为例
socket通信基本流程
通信基本流程如上图,在accept之后,客户端和服务端双方都获得了连接对方的Socket,随后即可不断地进行点对点地Send和Receive
通信demo
注:以下程序编译指令需加:-lwsock32
以Windows下,C++为例
顺序即实际流程顺序:
int socket(int domain, int type, int protocol);
//客户端和服务端均创建Socket,返回它的标识符,后续用这个int来代表这一socket
//domain: 协议族,常用AF_INET表示使用IPv4
//type:socket类型,常用SOCK_STREAM
//protocol:所用协议,常用IPPROTO_TCP
int bind(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen);
//服务端,绑定一个端口,返回错误信号
//用法见下,其中slisten服务端的socket标识符:
sockaddr_in sin; //这是一个地址对象
sin.sin_family = AF_INET;
sin.sin_port = htons(8888);//绑定的端口,要改的只有这里
sin.sin_addr.S_un.S_addr = INADDR_ANY;
if(bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR) {
cout << "bind error !\n";
}
int listen(SOCKET sockfd, int backlog);
//服务端,开始监听,返回错误信号(这是开启监听的命令,不是阻塞方法)
//backlog:最大可排队连接数
int connect(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen);
//客户端:连接服务端,返回错误信号
//用法如下,其中sclient为客户端的socket标识符:
sockaddr_in serAddr;
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons(8888);//端口
serAddr.sin_addr.S_un.S_addr = inet_addr("124.223.118.118");//IP
if(connect(sclient, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR) {
cerr << "connect error !\n";
}
int accept(SOCKET sockfd, struct sockaddr *addr, socklen_t *addrlen);
//服务端,阻塞方法,等待来自客户端的connect,等到后返回客户端的socket标识符
//到这一步,服务端知晓了客户端的SOCKET标识和IP,可以记录下来便于未来主动向客户端发送消息
int send(SOCKET sclient, const char *msg, int len, int flags);
int recv(SOCKET sclient, char *buf, int len, int flags);
//收发消息服务端和客户端均可使用,sclient处都使用accept方法得到的客户端的SOCKET标识即可
//这里的通信就自由了,客户度send,服务端recv,随后还可以服务端send,客户端recv进行一个回复;
另一提:网络间通信,由于网络波动等,一次消息的发送或接收失败很正常,应做出错误处理而非中断程序;
客户端:
#include<WINSOCK2.H>
#include<iostream>
#include<cstring>
#include<ctime>
using namespace std;
#pragma comment(lib, "ws2_32.lib")
const char *ipv4 = "124.223.118.118";
const unsigned short hton = 8888;
bool sendToServer(const char *msg){
//连接
SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(sclient == INVALID_SOCKET) {
cout << "invalid socket!\n";
return false;
}
sockaddr_in serAddr;
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons(hton);//端口
serAddr.sin_addr.S_un.S_addr = inet_addr(ipv4);//IP
if(connect(sclient, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR) {
//连接失败
cerr << "connect error !\n";
closesocket(sclient);
return false;
}
//send()用来将数据由指定的socket传给对方主机
if(send(sclient, msg, strlen(msg), 0) == -1){
cerr << "send failed!\n";
closesocket(sclient);
return false;
}
//recv()来自服务端的回信
char recData[255];
int ret = recv(sclient, recData, 255, 0);
if(ret>0) {
recData[ret] = 0x00;
cout << recData;
}
closesocket(sclient);
return true;
}
int main(){
//初始化
WORD sockVersion = MAKEWORD(2, 2);
WSADATA data;
if(WSAStartup(sockVersion, &data)!=0) {
return 0;
}
while(true) {
string data;
cin >> data;
sendToServer(data.c_str());
if(data == "END") break;
}
WSACleanup();
return 0;
}
服务端:
#include <iostream>
#include <stdio.h>
#include <winsock2.h>
using namespace std;
#pragma comment(lib,"ws2_32.lib")
int main(int argc, char* argv[]) {
//初始化WSA
WORD sockVersion = MAKEWORD(2,2);
WSADATA wsaData;
if(WSAStartup(sockVersion, &wsaData)!=0) {
return 0;
}
//创建套接字
SOCKET slisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(slisten == INVALID_SOCKET) {
cout << "socket error !\n";
return 0;
}
//绑定IP和端口
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(8888);
sin.sin_addr.S_un.S_addr = INADDR_ANY;
if(bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR) {
cout << "bind error !\n";
}
//开始监听
if(listen(slisten, 5) == SOCKET_ERROR) {
cout << "listen error !\n";
return 0;
}
//循环接收数据
SOCKET sClient;
sockaddr_in remoteAddr;
int nAddrlen = sizeof(remoteAddr);
char revData[255];
while (true) {
cout << "Waiting...\n";
sClient = accept(slisten, (SOCKADDR *)&remoteAddr, &nAddrlen);
if(sClient == INVALID_SOCKET)
{
cout << "accept error !\n";
continue;
}
cout << "received connect: " << inet_ntoa(remoteAddr.sin_addr) << " \r\n";
//接收数据
int ret = recv(sClient, revData, 255, 0);
if(ret > 0) {
revData[ret] = 0x00;
cout << revData << '\n';
}
//发送数据
const char * sendData = "how are you? \n";
send(sClient, sendData, strlen(sendData), 0);
closesocket(sClient);
}
closesocket(slisten);
WSACleanup();
return 0;
}
避个坑:服务端的回信不是必须的(三次握手在更加底层的地方,已经有人完成好了),此处只是为了方便查看通信结果;
初探 Socket
http://www.lxtyin.ac.cn/2022/01/17/2022-01-17-Socket入门/