五、socket网络编程
5.1 整体需要注意的点
需要注意的点:
-
windows的头文件是<WinSock2.h>,并且需要加入
#pragma comment(lib,"ws2_32.lib")
-
必须先执行WSAStartup,如果直接执行socket,那么会创建失败。
WSADATA wsaData;//接受// WORD wVerSion = MAKEWORD(2, 2);//版本号// if (WSAStartup(wVerSion, &wsaData)) //wsastartup()函数向操作系统说明,我们要用哪个库文件// wsastartup()主要就是进行相应的socket库绑定。 //初始化成功,返回0// { printf("WSAStartup\n"); return 0; }
-
另外需要加入下面命令,防止一些warning阻止程序运行。
#define _WINSOCK_DEPRECATED_NO_WARNINGS
5.2 创建服务器顺序:
- WSA的启动
- socket创建
- 绑定socket和ip
- 创建sockaddr_in结构体,把协议族(AF_INET),IP和port复制给该结构体。
- bind绑定,其中入参要将sockaddr_in强转为sockaddr。
- 监听socket,listen(),其作用是把socket从主动变为被动,从而成为一个服务器,可以使用accpet来接收连接。
- 1 以下可循环
- 接收连接,会产生新的fd。
- 接收数据
- 发送数据
- 关闭socket
//Serve端//
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include<stdio.h>
#include <iostream>
#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
class TCP {
public:
int Server() {
WSADATA wsaData;//接受//
WORD wVerSion = MAKEWORD(2, 2);//版本号//
if (WSAStartup(wVerSion, &wsaData)) //wsastartup()函数向操作系统说明,我们要用哪个库文件// wsastartup()主要就是进行相应的socket库绑定。 //初始化成功,返回0//
{
printf("WSAStartup\n");
return 0;
}
//PF_INET与AF_INET一样
//1、用于设置网络通信的域,函数socket()根据这个参数选择通信协议的族
//2、套接字类型
//3、套接字使用的协议
//这个函数建立一个协议族为domain、协议类型为type、协议编号为protocol的套接字文件描述符
SOCKET ServerFD;
ServerFD = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (ServerFD == -1) //INVALID_SOCKET = -1, 表示不是有效的套接字//
{
printf("socket 失败\n");
}
else {
printf("Server sock fd = %d\n", ServerFD);
}
//2 绑定socket
//2.1 先把协议族、ip和port放入sockaddr_in结构体
string ip_addr = "127.0.0.1";
int ip_port = 8090;
sockaddr_in sockaddr;
memset(&sockaddr, 0, sizeof(sockaddr));
sockaddr.sin_family = AF_INET;
sockaddr.sin_addr.s_addr = inet_addr(ip_addr.c_str());
sockaddr.sin_port = htons(ip_port);
//2.2 把初始化好的sockaddr_in(sockaddr) 绑定到 socket(ServerFD)
// 需要将sockaddr_in强转为sockaddr
if (::bind(ServerFD, (struct sockaddr*)&sockaddr, sizeof(sockaddr)) < 0) {
printf("绑定失败!\n");
}
else {
printf("绑定成功!\n");
}
//3 让该socket监听,使其真正成为服务器。才能使用accept接收连接。
if (::listen(ServerFD, 1024) < 0) {
printf("监听失败!\n");
}
else {
printf("正在监听中...\n");
}
while (true) {
//接收客户端连接
SOCKET acceptFD;
acceptFD = ::accept(ServerFD, nullptr, nullptr);
if (acceptFD < 0) {
printf("接收失败!\n");
return 0;
}
//从连接的套接字中接收数据。
char buf[1024] = { 0 };
size_t len = ::recv(acceptFD, buf, sizeof(buf), 0);
if (len < 0) {
printf("接收数据失败!\n");
}
else {
printf("len:%d\nmessage:%s\n", len, buf);
}
string response("I got the message!");
size_t send_len = ::send(acceptFD, response.c_str(), sizeof(response), 0);
if (send_len < 0) {
printf("发送失败!");
}
else {
printf("发送成功!");
}
}
::close(ServerFD);
return 0;
}
};
5.3 创建客户端顺序
- WSA初始化
- 创建客户端socket,socket
- 连接服务器,connect
- 以下可循环
- 发送数据
- 接收数据
- 关闭连接。
class TCPClient {
public:
int Client() {
WSADATA wsaData;//接受//
WORD wVerSion = MAKEWORD(2, 2);//版本号//
if (WSAStartup(wVerSion, &wsaData)) //wsastartup()函数向操作系统说明,我们要用哪个库文件// wsastartup()主要就是进行相应的socket库绑定。 //初始化成功,返回0//
{
printf("WSAStartup\n");
return 0;
}
//1. 创建客户端fd
SOCKET clientFD;
clientFD = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//2. 连接服务器
// 2.1 初始化sockaddr_in
string Server_IP = "127.0.0.1";
int Server_Port = 8090;
struct sockaddr_in sockaddr;
sockaddr.sin_family = AF_INET;
sockaddr.sin_addr.s_addr = inet_addr(Server_IP.c_str());
sockaddr.sin_port = htons(Server_Port);
// 2.2 连接服务器
if (::connect(clientFD, (struct sockaddr*)&sockaddr, sizeof(sockaddr)) < 0) {
printf("客户端连接失败!\n");
return 0;
}
//3. 发送数据
string request("hello Server");
size_t len;
len = ::send(clientFD, request.c_str(), sizeof(request), 0);
if (len < 0) {
printf("客户端发送数据失败!\n");
}
//4. 接收数据
char buf[1024] = { 0 };
len = ::recv(clientFD, buf, sizeof(buf), 0);
if (len < 0) {
printf("客户端接收数据失败!\n");
}
printf("客户端接收到数据\nlen:%d\nmessage:%s\n", len, buf);
//关闭客户端
::closesocket(clientFD);
return 0;
}
};
5.4 测试程序
#include "TCPServer.h"//我把TCPClient也放在了这个文件下
#include <thread>
using namespace std;
int main() {
thread t([]() {
TCPServer ts;
ts.Server();
});
TCPClient tc;
tc.Client();
}
5.5 测试结果
5.6 第一次改进优化
把socket操作的部分封装到一起
//Serve端//
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include<stdio.h>
#include <iostream>
#include <string>
#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
class Socket {
public:
void initWSA() {
WSADATA wsaData;//接受//
WORD wVerSion = MAKEWORD(2, 2);//版本号//
if (WSAStartup(wVerSion, &wsaData)) //wsastartup()函数向操作系统说明,我们要用哪个库文件// wsastartup()主要就是进行相应的socket库绑定。 //初始化成功,返回0//
{
printf("WSAStartup\n");
}
}
SOCKET createSocket() {
SOCKET SocketFD;
SocketFD = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (SocketFD == -1) //INVALID_SOCKET = -1, 表示不是有效的套接字//
{
printf("socket 失败\n");
}
else {
printf("Server sock fd = %d\n", SocketFD);
}
return SocketFD;
}
//TCPServer使用
void bindSocket(const SOCKET& SocketFD, string ip_addr = "127.0.0.1", int ip_port = 8090) {
//2 绑定socket
//2.1 先把协议族、ip和port放入sockaddr_in结构体
sockaddr_in sockaddr;
memset(&sockaddr, 0, sizeof(sockaddr));
sockaddr.sin_family = AF_INET;
sockaddr.sin_addr.s_addr = inet_addr(ip_addr.c_str());
sockaddr.sin_port = htons(ip_port);
//2.2 把初始化好的sockaddr_in(sockaddr) 绑定到 socket(ServerFD)
// 需要将sockaddr_in强转为sockaddr
if (::bind(SocketFD, (struct sockaddr*)&sockaddr, sizeof(sockaddr)) < 0) {
printf("绑定失败!\n");
}
else {
printf("绑定成功!\n");
}
}
//TCPClient使用
void connectSocket(const SOCKET& SocketFD, string ip_addr = "127.0.0.1", int ip_port = 8090) {
struct sockaddr_in sockaddr;
sockaddr.sin_family = AF_INET;
sockaddr.sin_addr.s_addr = inet_addr(ip_addr.c_str());
sockaddr.sin_port = htons(ip_port);
// 2.2 连接服务器
if (::connect(SocketFD, (struct sockaddr*)&sockaddr, sizeof(sockaddr)) < 0) {
printf("客户端连接失败!\n");
}
}
void listenSocket(const SOCKET& SocketFD) {
if (::listen(SocketFD, 1024) < 0) {
printf("监听失败!\n");
}
else {
printf("正在监听中...\n");
}
}
};
class TCPServer :public Socket{
public:
int Server() {
//1. 初始化WSA
initWSA();
//2. 创建服务器socket,获取其fd
SOCKET ServerFD = createSocket();
//3. 绑定服务器socket和IP
bindSocket(ServerFD);
//3 让该socket监听,使其真正成为服务器。才能使用accept接收连接。
listenSocket(ServerFD);
SOCKET acceptFD;
acceptFD = ::accept(ServerFD, nullptr, nullptr);
while (true) {
//接收客户端连接
if (acceptFD < 0) {
printf("接收失败!\n");
return 0;
}
//从连接的套接字中接收数据。
char buf[1024] = { 0 };
size_t len = ::recv(acceptFD, buf, sizeof(buf), 0);
if (len < 0) {
printf("接收数据失败!\n");
}
else {
printf("服务器端接收数据\nlen:%d\nmessage:%s\n", len, buf);
}
/*string response("hello client!");
size_t send_len = ::send(acceptFD, response.c_str(), sizeof(response), 0);
if (send_len < 0) {
printf("发送失败!");
}
else {
printf("服务器发送成功!");
}*/
//::closesocket(acceptFD);
}
::closesocket(ServerFD);
return 0;
}
};
class TCPClient :public Socket{
public:
int Client() {
//1. 初始化WSA
initWSA();
//2. 创建客户端fd
SOCKET clientFD = createSocket();
//3. 连接服务器
connectSocket(clientFD);
int i = 0;
//4. 发送数据
while (true) {
string request("hello Server");
request = request + to_string(i++);
size_t len;
len = ::send(clientFD, request.c_str(), sizeof(request), 0);
if (len < 0) {
printf("客户端发送数据失败!\n");
}
////5. 接收数据
//char buf[1024] = { 0 };
//len = ::recv(clientFD, buf, sizeof(buf), 0);
//if (len < 0) {
// printf("客户端接收数据失败!\n");
//}
//printf("客户端接收到数据\nlen:%d\nmessage:%s\n", len, buf);
}
//关闭客户端
::closesocket(clientFD);
return 0;
}
};
5.7第二次改进
将
- socket公共部分提出来
- TCPServer只实现listenSocket、bindSocket和acceptSocket
- TCPClient只实现connectSocket。
5.7.1 socket公共部分
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include<stdio.h>
#include <iostream>
#include <string>
#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
class Socket {
public:
Socket():_ip(""),_port(0),_sockfd(0) {
WSADATA wsaData;//接受//
WORD wVerSion = MAKEWORD(2, 2);//版本号//
if (WSAStartup(wVerSion, &wsaData)) //wsastartup()函数向操作系统说明,我们要用哪个库文件// wsastartup()主要就是进行相应的socket库绑定。 //初始化成功,返回0//
{
printf("WSAStartup\n");
}
_sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (_sockfd == -1) //INVALID_SOCKET = -1, 表示不是有效的套接字//
{
printf("socket 失败\n");
}
else {
printf("Server sock fd = %d\n", _sockfd);
}
}
SOCKET getSockFD() {
return _sockfd;
}
bool setIP(const string& ip) {
_ip = ip;
return true;
}
bool setPort(int port) {
_port = port;
return true;
}
private:
string _ip;
int _port;
SOCKET _sockfd;
};
5.7.2 TCPServer部分
class TCPServer :public Socket{
public:
TCPServer():_socket(new Socket){
}
//TCPServer使用
void bindSocket(string ip_addr = "127.0.0.1", int ip_port = 8090) {
//2 绑定socket
//2.1 先把协议族、ip和port放入sockaddr_in结构体
sockaddr_in sockaddr;
memset(&sockaddr, 0, sizeof(sockaddr));
sockaddr.sin_family = AF_INET;
sockaddr.sin_addr.s_addr = inet_addr(ip_addr.c_str());
sockaddr.sin_port = htons(ip_port);
//2.2 把初始化好的sockaddr_in(sockaddr) 绑定到 socket(ServerFD)
// 需要将sockaddr_in强转为sockaddr
if (::bind(_socket->getSockFD(), (struct sockaddr*)&sockaddr, sizeof(sockaddr)) < 0) {
printf("绑定失败!\n");
}
else {
printf("绑定成功!\n");
}
_socket->setIP(ip_addr);
_socket->setPort(ip_port);
}
void listenSocket(int backlog) {
if (::listen(_socket->getSockFD(), backlog) < 0) {
printf("监听失败!\n");
}
else {
printf("正在监听中...\n");
}
}
int Server() {
////1. 初始化WSA
//initWSA();
////2. 创建服务器socket,获取其fd
//SOCKET ServerFD = createSocket();
//3. 绑定服务器socket和IP
bindSocket();
//3 让该socket监听,使其真正成为服务器。才能使用accept接收连接。
auto serverfd = _socket->getSockFD();
listenSocket(serverfd);
SOCKET acceptFD;
acceptFD = ::accept(serverfd, nullptr, nullptr);
while (true) {
//接收客户端连接
if (acceptFD < 0) {
printf("接收失败!\n");
return 0;
}
//从连接的套接字中接收数据。
char buf[1024] = { 0 };
size_t len = ::recv(acceptFD, buf, sizeof(buf), 0);
if (len < 0) {
printf("接收数据失败!\n");
}
else {
printf("服务器端接收数据\nlen:%d\nmessage:%s\n", len, buf);
}
/*string response("hello client!");
size_t send_len = ::send(acceptFD, response.c_str(), sizeof(response), 0);
if (send_len < 0) {
printf("发送失败!");
}
else {
printf("服务器发送成功!");
}*/
//::closesocket(acceptFD);
}
::closesocket(serverfd);
return 0;
}
private:
Socket *_socket;
};
5.7.3 TCPClient部分
class TCPClient :public Socket{
public:
TCPClient() :_socket(new Socket){
}
//TCPClient使用
void connectSocket(string ip_addr = "127.0.0.1", int ip_port = 8090) {
struct sockaddr_in sockaddr;
sockaddr.sin_family = AF_INET;
sockaddr.sin_addr.s_addr = inet_addr(ip_addr.c_str());
sockaddr.sin_port = htons(ip_port);
// 2.2 连接服务器
if (::connect(_socket->getSockFD(), (struct sockaddr*)&sockaddr, sizeof(sockaddr)) < 0) {
printf("客户端连接失败!\n");
}
}
int Client() {
SOCKET clientFD = _socket->getSockFD();
//3. 连接服务器
connectSocket();
int i = 0;
//4. 发送数据
while (true) {
string request("hello Server");
request = request + to_string(i++);
size_t len;
len = ::send(clientFD, request.c_str(), sizeof(request), 0);
if (len < 0) {
printf("客户端发送数据失败!\n");
}
////5. 接收数据
//char buf[1024] = { 0 };
//len = ::recv(clientFD, buf, sizeof(buf), 0);
//if (len < 0) {
// printf("客户端接收数据失败!\n");
//}
//printf("客户端接收到数据\nlen:%d\nmessage:%s\n", len, buf);
}
//关闭客户端
::closesocket(clientFD);
return 0;
}
private:
Socket *_socket;
};
5.7.4 测试代码
#include "TCP.h"
#include <thread>
#include <Windows.h>
using namespace std;
int main() {
thread t([]() {
TCPServer ts;
ts.Server();
});
TCPClient tc;
tc.Client();
Sleep(2000);
}