1.引言-什么是socket
socket即套接字,用于描述地址和端口,是一个通信链的句柄。应用程序通过socket向网络发出请求或者回应。
sockets(套接字)编程有三种,流式套接字(SOCK_STREAM),数据报套接字(SOCK_DGRAM),原始套接字(SOCK_RAW);前两种较常用。基于TCP的socket编程是采用的流式套接字。
有可能多种协议使用同一种数据传输方式,所以在socket编程中,需要同时指明数据传输方式和协议。
2. socket常用函数
2.1 sockaddr_in
struct sockaddr_in这个结构体用来处理网络通信的地址
struct sockaddr_in {
short sin_family; // 2 bytes e.g. AF_INET, AF_INET6
unsigned short sin_port; //16位 2 bytes e.g. htons(3490)
struct in_addr sin_addr; //32位 4 bytes see struct in_addr, below
char sin_zero[8]; // 8 bytes zero this if you want to
};
//另一个结构体 in_addr存放32位ip地址
struct in_addr {
unsigned long s_addr; // 4 bytes load with inet_pton()
};
端口号需要用 htons() 函数转换
2.2 htons()、 inet_addr()和inet_ntoa()
htons()作用是将端口号由主机字节序转换为网络字节序的整数值。(host to net)
inet_addr()作用是将一个IP字符串转化为一个网络字节序的整数值,用于sockaddr_in.sin_addr.s_addr。
inet_ntoa()作用是将一个sin_addr结构体输出成IP字符串(network to ascii)。比如:
printf("%s",inet_ntoa(mysock.sin_addr));
htonl()作用和htons()一样,不过它针对的是32位的(long),而htons()针对的是两个字节,16位的(short)。
与htonl()和htons()作用相反的两个函数是:ntohl()和ntohs()。
2.3 socket()
创建套接字
int socket(int af, int type, int protocol);
af:IP地址的类型
-
AF_INET : IPv4
-
AF_INET6: IPV6
type:数据传输方式
-
SOCK_STREAM:面向连接的数据传输方式
-
SOCK_DGRAM:无连接的数据传输方式
protocol:传输协议
-
IPPROTO_TCP:TCP传输协议
-
IPPTOTO_UDP:UDP传输协议
返回值
-
成功:0
-
失败:-1
2.4 bind()
地址绑定,将套接字与地址关联
int bind(int sockfd, struct sockaddr *addr, socklen_t addrlen);
sockfd:socket文件描述符
addr:sockaddr 结构体变量的指针
addrlen:addr 变量的大小
返回值
-
成功:0
-
失败:-1
2.5 connect()
建立连接,创建与指定外部端口的连接
int connect(int sockfd, struct sockaddr *serv_addr, socklen_t addrlen);
sockfd:socket文件描述符
addr:sockaddr 结构体变量的指针
addrlen:addr 变量的大小
返回值
-
成功:0
-
失败:-1
2.6 listen()
让套接字进入被动监听状态(指当没有客户端请求时,套接字处于”睡眠“状态,只有当接收到客户端请求时,套接字才会被“唤醒”来响应请求),使得一个进程可以接收其他进程的请求,从而成为一个服务器进程
int listen(int sockfd, int backlog);
sockfd:被监听的套接字的标识符
backlog:请求队列的最大长度(能存放多少个客户端请求)
-
请求队列:当套接字正在处理客户端请求时,如果有新的请求进来,套接字将把新的请求放入缓冲区,再从缓冲区取出请求 ,此缓冲区称为请求队列
返回值
-
成功:0
-
失败:-1
2.7 accept()
在一个套接口接收一个连接,当套接字处于监听状态时,可以通过accept()函数来接受客户端请求,accept()会阻塞程序进行,直到有新的请求到来
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
sockfd:服务器端套接字的标识符
addr:sockaddr 结构体变量的指针
addrlen:addr 变量的大小
返回值
-
成功:返回接收到的套接字的描述符
-
失败:-1
2.8 send()
发送数据,将数据由指定的socket传给对方主机
int send(int s, const void * msg, int len, unsigned int falgs);
s:以建立好连接的socket标识符
msg:发送的消息内容
len:发送内容的长度
falgs:一般设为0
返回值
-
成功:返回实际传送出去的字符数
-
失败:-1
2.9 recv()
接受数据,接收远端主机经过指定的socket传来的数据,并把数据存到buf指定的内存空间
int recv(int sock, void *buf, int len, unsigned int flags);
sock:接收端套接字描述符
buf:指定缓冲区,存放接收到的数据
len:缓冲区的长度
flags:一般设为0
返回值
-
成功:返回接收到的字符数
-
失败:-1
3.客户端/服务端模式
在TCP/IP(Transmission Control Protocol Internet Protocol / 传输控制协议和网际协议,TCP/IP是一种网络协议,由TCP和IP两个协议组成。它负责在计算机网络中传输数据,并确保数据传输的可靠性,同时确定数据在网络中的路径。TCP/IP是网络通信的基础,也是网络上最常用的协议之一)网络应用中,通信的两个进程相互作用的主要模式是客户/服务器模式,即客户端向服务器发出请求,服务器接收请求后,提供相应的服务。因此需要一种机制为希望通信的进程间建立联系,为二者的数据交换提供同步,这就是基于客户/服务端模式的TCP/IP。
3.1 什么是TCP
TCP有三个关键步骤:三次握手,传输确认和四次挥手
构建思路:
服务端:建立socket,声明自身的端口号和地址并绑定到socket,使用listen打开监听,然后不断用accept去查看是否有连接,如果有,捕获socket,并通过recv获取消息的内容,通信完成后调用closeSocket关闭这个对应accept到的socket,如果不再需要等待任何客户端连接,那么用closeSocket关闭掉自身的socket。
客户端:建立socket,通过端口号和地址确定目标服务器,使用Connect连接到服务器,send发送消息,等待处理,通信完成后调用closeSocket关闭socket。
3.2 编程步骤
(1)服务端
1、加载套接字库,创建套接字(WSAStartup()/socket());
2、绑定套接字到一个IP地址和一个端口上(bind());
3、将套接字设置为监听模式等待连接请求(listen());
4、请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字(accept());
5、用返回的套接字和客户端进行通信(send()/recv());
6、返回,等待另一个连接请求;
7、关闭套接字,关闭加载的套接字库(closesocket()/WSACleanup());
(2)客户端
1、加载套接字库,创建套接字(WSAStartup()/socket());
2、向服务器发出连接请求(connect());
3、和服务器进行通信(send()/recv());
4、关闭套接字,关闭加载的套接字库(closesocket()/WSACleanup());
3.3 学习代码
#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")
int main(int argc, char* argv[])
{
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)
{
printf("socket error !");
return 0;
}
sockaddr_in sin;
sin.sin_port = htons(8888);
sin.sin_family = AF_INET;
sin.sin_addr.S_un.S_addr = INADDR_ANY;
if (bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
{
printf("bind error !");
}
if (listen(slisten, 5) == SOCKET_ERROR)
{
printf("listen error !");
return 0;
}
SOCKET sClient;
sockaddr_in remoteAddr;
int nAddrlen = sizeof(remoteAddr);
char revData[255];
while (true)
{
printf("Wating for connecting... \r\n");
sClient = accept(slisten, (SOCKADDR *)&remoteAddr, &nAddrlen);
if (sClient == INVALID_SOCKET)
{
printf("accept error !");
continue;
}
printf("accept a connection: %s \r\n", inet_ntoa(remoteAddr.sin_addr));
int ret = recv(sClient, revData, 255, 0);
if (ret > 0)
{
revData[ret] = 0x00;
printf(revData);
}
const char * sendData = "hello, TCP Client";
send(sClient, sendData, strlen(sendData), 0);
closesocket(sClient);
}
closesocket(slisten);
WSACleanup();
return 0;
}
client.cpp
#include <WINSOCK2.H>
#include <STDIO.H>
#include <iostream>
#include <cstring>
using namespace std;
#pragma comment(lib, "ws2_32.lib")
int main()
{
WORD sockVersion = MAKEWORD(2,2);
WSADATA data;
if (WSAStartup(sockVersion, &data) != 0)
{
return 0;
}
while (true)
{
SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sclient == INVALID_SOCKET)
{
printf("Invalid socket!");
return 0;
}
sockaddr_in serAddr;
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons(8888);
serAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
if (connect(sclient, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
{
printf("connect error!");
closesocket(sclient);
return 0;
}
string data;
cin>>data;
const char * sendData;
sendData = data.c_str();
send(sclient, sendData, strlen(sendData), 0);
char recData[255];
int ret = recv(sclient, recData, 255, 0);
if (ret > 0)
{
recData[ret] = 0x00;
printf(recData);
}
closesocket(sclient);
}
WSACleanup();
return 0;
}
3.4 客户端一直发送数据代码
server.cpp
#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")
int main(int argc, char* argv[])
{
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)
{
printf("socket error!");
return 0;
}
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(6000);
sin.sin_addr.S_un.S_addr = INADDR_ANY;
if(bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
{
printf("bind error!");
return 0;
}
if (listen(slisten, 5) == SOCKET_ERROR)
{
printf("listen error!");
return 0;
}
SOCKET sClient;
sockaddr_in remoteAddr;
int nAddrlen = sizeof(remoteAddr);
char revData[255];
while (true)
{
printf("Waiting for connecting... \n");
sClient = accept(slisten, (SOCKADDR *)&remoteAddr, &nAddrlen);
if (sClient == SOCKET_ERROR)
{
printf("accept error !");
continue;
}
int ret = recv(sClient, revData, 255, 0);
if (ret > 0 )
{
revData[ret] = 0x00;
printf("Received: %s\n", revData);
}
const char * sendData = "Hello World from Server";
send(sClient, sendData, strlen(sendData), 0);
printf("Sent: %s\n", sendData);
// Keep receiving messages
while (true)
{
ret = recv(sClient, revData, 255, 0);
if (ret > 0)
{
revData[ret] = 0x00;
printf("Received: %s\n", revData);
}
}
closesocket(sClient);
}
closesocket(slisten);
WSACleanup();
return 0;
}
client.cpp
#include <winsock2.h>
#include <stdio.h>
#include <iostream>
#include <cstring>
#pragma comment(lib, "ws2_32.lib")
int main()
{
WORD sockVersion = MAKEWORD(2,2);
WSADATA data;
if(WSAStartup(sockVersion, &data) != 0)
{
return 0;
}
SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(sclient == INVALID_SOCKET)
{
printf("Invalid socket!");
return 0;
}
sockaddr_in serAddr;
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons(6000);
serAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
if (connect(sclient, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
{
printf("connect error!");
closesocket(sclient);
return 0;
}
const char * sendData = "Hello World from Client";
send(sclient, sendData, strlen(sendData), 0);
printf("Sent: %s\n", sendData);
char recData[255];
int ret = recv(sclient, recData, 255, 0);
if (ret > 0)
{
recData[ret] = 0x00;
printf("Received: %s\n", recData);
}
// Keep sending messages
while (true)
{
const char * helloData = "Hello";
send(sclient, helloData, strlen(helloData), 0);
printf("Sent: %s\n", helloData);
// Sleep for 1 second
Sleep(1000);
}
closesocket(sclient);
WSACleanup();
return 0;
}
3.5 用户在客户端输入数据代码
server.cpp
#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")
int main(int argc, char* argv[])
{
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)
{
printf("socket error!");
return 0;
}
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(6000);
sin.sin_addr.S_un.S_addr = INADDR_ANY;
if(bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
{
printf("bind error!");
return 0;
}
if (listen(slisten, 5) == SOCKET_ERROR)
{
printf("listen error!");
return 0;
}
SOCKET sClient;
sockaddr_in remoteAddr;
int nAddrlen = sizeof(remoteAddr);
char revData[255];
while (true)
{
printf("Waiting for connecting... \n");
sClient = accept(slisten, (SOCKADDR *)&remoteAddr, &nAddrlen);
if (sClient == SOCKET_ERROR)
{
printf("accept error !");
continue;
}
// Keep receiving messages
while (true)
{
int ret = recv(sClient, revData, 255, 0);
if (ret > 0)
{
revData[ret] = 0x00;
printf("Received: %s\n", revData);
}
else if (ret == 0) // Connection closed
{
printf("Client disconnected.\n");
closesocket(sClient);
break;
}
else if (ret == SOCKET_ERROR) // Error receiving
{
printf("recv error!\n");
closesocket(sClient);
break;
}
}
}
closesocket(slisten);
WSACleanup();
return 0;
}
client.cpp
#include <winsock2.h>
#include <stdio.h>
#include <iostream>
#include <cstring>
#pragma comment(lib, "ws2_32.lib")
int main()
{
WORD sockVersion = MAKEWORD(2,2);
WSADATA data;
if(WSAStartup(sockVersion, &data) != 0)
{
return 0;
}
SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(sclient == INVALID_SOCKET)
{
printf("Invalid socket!");
return 0;
}
sockaddr_in serAddr;
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons(6000);
serAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
if (connect(sclient, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
{
printf("connect error!");
closesocket(sclient);
return 0;
}
char sendData[255];
while (true)
{
std::cout << "Enter message: ";
std::cin.getline(sendData, 255);
if (strlen(sendData) == 0) {
continue;
}
send(sclient, sendData, strlen(sendData), 0);
printf("Sent: %s\n", sendData);
}
closesocket(sclient);
WSACleanup();
return 0;
}
4.在CLion建立客户端和服务端
修改CLion配置如下图
切换到此,点击运行即可进行通信