主要是用来记录自己在项目中遇到要使用socket中的tcp/ip通信。因为该项目只用tcp/ip,所以对于udp,http这些不做记录
这些例子的学习在网上一大堆,这个是我学习的链接,这个链接里面用了多线程,一个线程用来读,一个线程用来写,总的来说都比较简单,属于有手就会,也不难理解
在server端代码
#include <iostream>
#include <winsock.h>
#pragma comment(lib, "ws2_32.lib")
using namespace std;
void initialization();
int main()
{
// 定义长度变量
int send_len = 0;
int recv_len = 0;
int len = 0;
// 发送和接收缓冲区
char send_buf[100];
char recv_buf[100];
// 服务端套接字,接收请求套接字
SOCKET s_server;
SOCKET s_accept;
// 服务端地址和客户端地址
SOCKADDR_IN server_addr;
SOCKADDR_IN accept_addr;
initialization();
// 服务端信息
server_addr.sin_family = AF_INET;
server_addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(5060);
// 创建套接字 绑定
s_server = socket(AF_INET, SOCK_STREAM, 0);
if (bind(s_server, (SOCKADDR*)&server_addr, sizeof(SOCKADDR)) == SOCKET_ERROR)
{
cout << "绑定失败" << endl;
}
else
{
cout << "绑定成功" << endl;
}
// 监听
if (listen(s_server, SOMAXCONN) < 0)
{
cout << "设置监听状态失败!" << endl;
WSACleanup();
}
else
{
cout << "设置监听状态成功!" << endl;
}
cout << "服务端正在监听连接,请稍后......" << endl;
// 连接请求
len = sizeof(SOCKADDR);
s_accept = accept(s_server, (SOCKADDR*)&accept_addr, &len);
if (s_accept == SOCKET_ERROR)
{
cout << "连接失败" << endl;
WSACleanup();
return 0;
}
cout << "建立连接,准备接收数据" << endl;
// 接收数据
while (1)
{
// 从 recv 获取消息内容
recv_len = recv(s_accept, recv_buf, 100, 0);
if (recv_len < 0)
{
cout << "接收失败!" << endl;
break;
}
else
{
cout << "客户端信息:" << recv_buf << endl;
}
cout << "请输入回复信息:" << endl;
cin >> send_buf;
send_len = send(s_accept, send_buf, 100, 0);
if (send_len < 0)
{
cout << "发送失败" << endl;
break;
}
}
// 关闭套接字
closesocket(s_server);
closesocket(s_accept);
WSACleanup();
return 0;
}
void initialization()
{
WORD w_req = MAKEWORD(2, 2);
WSADATA wsadata;
int err;
err = WSAStartup(w_req, &wsadata);
if (err != 0)
{
cout << "初始化套接字失败" << endl;
}
else
{
cout << "初始化套接字成功" << endl;
}
if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wHighVersion) != 2)
{
cout << "套接字版本号不符!" << endl;
WSACleanup();
}
else
{
cout << "套接字版本正确!" << endl;
}
}
在client端代码
#include <iostream>
#include <winsock.h>
#pragma comment(lib, "ws2_32.lib")
using namespace std;
void initialization();
int main()
{
// 定义变量长度
int send_len = 0;
int recv_len = 0;
// 定义发送缓冲区和接收缓冲区
char send_buf[100];
char recv_buf[100];
// 定义服务端套接字,接受请求套接字
SOCKET s_server;
// 服务端地址
SOCKADDR_IN server_addr;
initialization();
// 填充信息
server_addr.sin_family = AF_INET;
server_addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
server_addr.sin_port = htons(5060);
// 连接服务端
s_server = socket(AF_INET, SOCK_STREAM, 0);
if (connect(s_server, (SOCKADDR*)&server_addr, sizeof(SOCKADDR)) == SOCKET_ERROR)
{
cout << "服务器连接失败" << endl;
WSACleanup();
}
else
{
cout << "服务器连接成功" << endl;
}
while (1)
{
cout << "请输入发送数据:";
cin >> send_buf;
send_len = send(s_server, send_buf, 100, 0);
if (send_len < 0)
{
cout << "发送失败!" << endl;
break;
}
recv_len = recv(s_server, recv_buf, 100, 0);
if (recv_len < 0)
{
cout << "接收失败" << endl;
break;
}
else
{
cout << "服务端消息:" << recv_buf << endl;
}
}
closesocket(s_server);
WSACleanup();
return 0;
}
void initialization()
{
WORD w_req = MAKEWORD(2, 2);
WSADATA wsadata;
int err;
err = WSAStartup(w_req, &wsadata);
if (err != 0)
{
cout << "初始化套接字失败" << endl;
}
else
{
cout << "初始化套接字成功" << endl;
}
if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wHighVersion) != 2)
{
cout << "套接字版本号不符!" << endl;
WSACleanup();
}
else
{
cout << "套接字版本正确!" << endl;
}
}
运行上面的代码只需要新建两个VS项目,在不同的项目里面分别运行服务端和客户端就可以了。如果是两台电脑,客户端的IP那里要修改。
上面代码是纯C++的,之前学习socket的时候是学的QT,所以附上QT的代码,在QT里面是写的一个mainwindow类
server端
// mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->port->setText("8899");
setWindowTitle("服务器 ");
// 1. 创建监听的服务器对象
m_s = new QTcpServer(this);
// 3. 检测有没有客户端连接
// 该槽函数不是在构造函数中被执行的。当某一个信号被发送出来时,对应的槽函数才被执行,是一个回调机制。
// connect 只是做了一个注册,告诉QT框架在什么时机,什么条件下做什么事情
connect(m_s, &QTcpServer::newConnection, this, [=](){
// 在第2步监听完之后 QT框架检测 QTcpServer 有没有发射出 newConnection 信号,如果有信号表示有客户端连接到达,服务器就会和客户端建立连接,
// 建立连接完之后就会得到一个用于通信的套接字对象 m_tcp,m_tcp指向一块有效的堆内存
// 如果有多个客户端同时通信,那么就会得到多个套接字对象,此时应该怎么处理?
// 在内存析构这里,m_tcp是子对象,m_s是父对象,所以m_s析构的时候会先析构m_tcp
// 3.1 有客户端连接,得到一个新的套接字对象
m_tcp = m_s->nextPendingConnection();
// 连接成功,把状态栏的标签替换
m_status->setPixmap(QPixmap(":/连接.jpg").scaled(20, 20));
// 3.2 检查是否可以接受数据
connect(m_tcp, &QTcpSocket::readyRead, this, [=](){
// 进行数据接收
QByteArray data = m_tcp->readAll();
ui->record->append("客户端say:" + data);
});
// 3.3 检测客户端状态连接,服务器也需要断开,双向断开,如果未连接就断开,状态标签替换
connect(m_tcp, &QTcpSocket::disconnected, this, [=](){
m_tcp->close();
m_tcp->deleteLater(); // 封装了 delete
m_status->setPixmap(QPixmap(":/连接失败.jpg").scaled(20, 20));
});
});
// 状态栏的处理动作
m_status = new QLabel;
m_status->setPixmap(QPixmap(":/连接失败.jpg").scaled(20, 20));
ui->statusBar->addWidget(new QLabel("连接状态:"));
ui->statusBar->addWidget(m_status);
}
MainWindow::~MainWindow()
{
delete ui;
}
// 2. 监听
void MainWindow::on_setListen_clicked()
{
unsigned short port = ui->port->text().toUShort();
// 2. 监听 port要指定固定的端口最大值为65535,最小值为0,建议为5000以上
m_s->listen(QHostAddress::Any, port);
// 将 启动监听服务 按钮置灰
ui->setListen->setDisabled(true);
}
// 4. 给客户端回复数据的时间
void MainWindow::on_sendMassage_clicked()
{
// toPlainText 将数据以纯文本的方式取出来
QString msg = ui->msg->toPlainText();
// 4. 调用 write 方法把数据发送给客户端
m_tcp->write(msg.toUtf8());
ui->record->append("服务端say:" + msg);
}
下面是server的.h文件
// mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QTcpServer>
#include <QTcpSocket>
#include <QLabel>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void on_setListen_clicked();
void on_sendMassage_clicked();
private:
Ui::MainWindow *ui;
QTcpServer* m_s;
QTcpSocket* m_tcp;
QLabel* m_status;
};
#endif // MAINWINDOW_H
client端
// mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QHostAddress>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->ip->setText("127.0.0.1"); // 127.0.0.1表示本机
ui->port->setText("8899");
setWindowTitle("客户端 ");
ui->disconnect->setDisabled(true);
// 1. 创建监听的客户端对象
m_tcp = new QTcpSocket;
// 3.2 检查是否可以接受数据
connect(m_tcp, &QTcpSocket::readyRead, this, [=](){
// QT中使用 QTcpSocket 进行数据的操作,操作的数据不是直接对网络中的数据进行操作,是本地的数据。
// 如:B给A发送数据,QT会维护一块内存存放B发送过来的数据,在A这里通过 readAll 读取的是QT本地那块内存的数据
// 进行数据接收
QByteArray data = m_tcp->readAll();
ui->record->append("服务端say:" + data);
});
// 3.3 检测客户端状态连接,如果未连接就断开,状态标签替换
// 检测到断开连接是对方端断开了连接
connect(m_tcp, &QTcpSocket::disconnected, this, [=](){
m_tcp->close();
m_tcp->deleteLater(); // 封装了 delete 在此处释放了,所以在创建监听的客户端对象那里就不需要this了
m_status->setPixmap(QPixmap(":/连接失败.jpg").scaled(20, 20));
ui->record->append("已经和服务器断开连接");
ui->connect->setDisabled(false);
ui->disconnect->setEnabled(false);
});
// 在①之后,如果信号被成功连接,则信号被发射
// 这里的 connected 检测只能在客户端检测,服务端不能检测
connect(m_tcp, &QTcpSocket::connected, this, [=](){
m_status->setPixmap(QPixmap(":/连接.jpg").scaled(20, 20));
ui->record->append("已经连接成功到服务器....");
ui->connect->setDisabled(true);
ui->disconnect->setEnabled(true);
});
// 状态栏的处理动作
m_status = new QLabel;
m_status->setPixmap(QPixmap(":/连接失败.jpg").scaled(20, 20));
ui->statusBar->addWidget(new QLabel("连接状态:"));
ui->statusBar->addWidget(m_status);
}
MainWindow::~MainWindow()
{
delete ui;
}
// 4. 给客户端回复数据的时间
void MainWindow::on_sendMassage_clicked()
{
QString msg = ui->msg->toPlainText();
// 4. 调用 write 方法把数据发送给客户端
m_tcp->write(msg.toUtf8());
ui->record->append("客户端say:" + msg);
}
// 1. 连接服务器
void MainWindow::on_connect_clicked()
{
QString ip = ui->ip->text();
unsigned short port = ui->port->text().toUShort();
// ① connectToHost用于客户端连接服务器 port是服务器绑定的端口
m_tcp->connectToHost(QHostAddress(ip), port);
}
void MainWindow::on_disconnect_clicked()
{
m_tcp->close();
ui->connect->setDisabled(false);
ui->disconnect->setEnabled(false);
}
同样的.h文件如下
// mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QTcpSocket>
#include <QLabel>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void on_sendMassage_clicked();
void on_connect_clicked();
void on_disconnect_clicked();
private:
Ui::MainWindow *ui;
QTcpSocket* m_tcp;
QLabel* m_status;
};
#endif // MAINWINDOW_H
后来的时候同事给我说使用libevent库更好用,后面学习了一下就用libevent库了,简单的实现了一下TCP/IP,反正都不难,下面这块代码是在网上找的,具体连接我找不到了,先把代码放这
server端
// libevent_server.cpp
#include "libevent_server.h"
#include <iostream>
#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <event2/listener.h>
#include <event2/event.h>
libevent_server::libevent_server()
{
}
libevent_server::~libevent_server()
{
}
void libevent_server::conn_readcb(struct bufferevent *bev, void *user_data)
{
char buf[1024] = { 0 };
直接将数据从bufferevent中读取出来
//bufferevent_read(bev, buf, sizeof(buf));
//int length = strlen(buf);
//if (length)
//{
// std::cout << "server recv msg:" << buf << std::endl;
//}
// 将数据从bufferevent存放到evbuffer中
struct evbuffer *input = bufferevent_get_input(bev);
int length = evbuffer_get_length(input);
if (length)
{
evbuffer_remove(input, buf, sizeof(buf));
std::cout << "server recv msg:" << buf << std::endl;
bufferevent_getfd(bev);
bufferevent_write(bev, buf, strlen(buf) + 1);
}
memset(buf, 0, 1024);
return;
}
void libevent_server::conn_writecb(struct bufferevent *bev, void *user_data)
{
// listen server send message. if server not send message will not trigger this callback
// if send message not use evbuffer, then bufferevent_get_input can not get the message
char buf[1024] = { 0 };
struct evbuffer *output = bufferevent_get_output(bev);
int length = evbuffer_get_length(output);
if (length)
{
evbuffer_copyout(output, buf, sizeof(buf));
std::cout << "server send msg:" << buf << std::endl;
}
evbuffer_free(output);
memset(buf, 0, 1024);
return;
}
void libevent_server::conn_eventcb(struct bufferevent *bev, short events, void *user_data)
{
if (events & BEV_EVENT_EOF)
{
std::cout << "Connection closed." << std::endl;
}
else if (events & BEV_EVENT_ERROR)
{
std::cout << "Got an error on the connection." << std::endl;
}
else if (events & BEV_EVENT_CONNECTED)
{
std::cout << "Connection client success." << std::endl;
}
/* None of the other events can happen here, since we haven't enabled timeouts */
bufferevent_free(bev);
return;
}
void libevent_server::listener_cb(struct evconnlistener *listener, evutil_socket_t fd, struct sockaddr *sa, int socklen, void *user_data)
{
struct event_base *base = (struct event_base *)user_data;
struct bufferevent *bev;
bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
if (!bev)
{
std::cout << "Error constructing bufferevent!" << std::endl;
event_base_loopbreak(base);
return;
}
else
{
std::cout << "连接成功!" << std::endl;
}
bufferevent_setcb(bev, conn_readcb, NULL, conn_eventcb, NULL);
bufferevent_enable(bev, EV_READ | EV_WRITE);
const char *p = "connect success!";
bufferevent_write(bev, p, strlen(p) + 1);
return;
}
void libevent_server::initial(int port)
{
m_port = port;
m_sin = { 0 };
m_sin.sin_family = AF_INET;
m_sin.sin_port = htons(m_port);
#ifdef _WIN32
WSADATA wsa_data;
WSAStartup(0x0201, &wsa_data);
#endif
m_base = event_base_new();
if (!m_base)
{
std::cout << "not initial libevent." << std::endl;
return;
}
return;
}
void libevent_server::creat_bind_accept()
{
// 绑定
m_listener = evconnlistener_new_bind(m_base, listener_cb, (void *)m_base, LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE, -1,
(struct sockaddr*)&m_sin, sizeof(m_sin));
// 启动循环监听
event_base_dispatch(m_base);
// 释放
evconnlistener_free(m_listener);
event_base_free(m_base);
return;
}
下面是.h文件
// libevent_server.h
#pragma once
#include <event2/util.h>
class libevent_server
{
public:
libevent_server();
~libevent_server();
void initial(int port);
void creat_bind_accept();
private:
struct sockaddr_in m_sin;
int m_port;
struct event_base *m_base;
struct evconnlistener *m_listener;
private:
static void conn_readcb(struct bufferevent *bev, void *user_data);
static void conn_writecb(struct bufferevent *bev, void *user_data);
static void conn_eventcb(struct bufferevent *bev, short events, void *user_data);
static void listener_cb(struct evconnlistener *listener, evutil_socket_t fd, struct sockaddr *sa, int socklen, void *user_data);
};
在mian函数里面直接调用就可以了
#include <iostream>
#include <string>
#include "libevent_server.h"
int main()
{
libevent_server server;
server.initial(6000);
server.creat_bind_accept();
}
在客户端
#include "libevent_client.h"
#include <iostream>
#include <stdio.h>
#include <windows.h>
#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <event2/listener.h>
#include <event2/event.h>
libevent_client::libevent_client()
{
}
libevent_client::~libevent_client()
{
}
void libevent_client::initial(int port)
{
m_port = port;
memset(&m_sin, 0, sizeof(m_sin));
m_sin.sin_family = AF_INET;
m_sin.sin_port = htons(m_port);
//m_sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
inet_pton(AF_INET, "192.168.2.69", &m_sin.sin_addr.s_addr);
#ifdef _WIN32
WSADATA wsa_data;
WSAStartup(0x0201, &wsa_data);
#endif
// 创建base
m_base = event_base_new();
// 将套接字和base关联起来,得到bufferevent
m_bev = bufferevent_socket_new(m_base, -1, BEV_OPT_CLOSE_ON_FREE);
if (!m_bev)
{
std::cout << "error , create socket buffer." << std::endl;
return;
}
}
void libevent_client::read_cb(struct bufferevent *bev, void *arg)
{
char buf[1024] = { 0 };
//直接从bufferevent将数据读取出来
bufferevent_read(bev, buf, sizeof(buf));
int length = strlen(buf);
if (length)
{
std::cout << "client recv msg:" << buf << std::endl;
}
// // 从bufferevent将数据读取出来,存放到evbuffer中
//struct evbuffer *input = bufferevent_get_input(bev);
//int length = evbuffer_get_length(input);
//if (length)
//{
// evbuffer_remove(input, buf, sizeof(buf));
// std::cout << "client recv msg:" << buf << std::endl;
//}
memset(buf, 0, 1024);
return;
}
void libevent_client::write_cb(struct bufferevent *bev, void *arg)
{
listen client send message. if client not send message will not trigger this callback
if send message not use evbuffer, then bufferevent_get_input can not get the message
//char buf[1024] = { 0 };
//struct evbuffer *output = bufferevent_get_output(bev);
//int length = evbuffer_get_length(output);
//if (length)
//{
// evbuffer_remove(output, buf, sizeof(buf));
// std::cout << "client send msg:" << buf << std::endl;
//}
//memset(buf, 0, 1024);
return;
}
void libevent_client::event_cb(struct bufferevent *bev, short events, void *arg)
{
if (events & BEV_EVENT_EOF)
{
std::cout << "Connection closed." << std::endl;
bufferevent_free(bev);
}
else if (events & BEV_EVENT_ERROR)
{
std::cout << "Got an error on the connection." << std::endl;
bufferevent_free(bev);
}
else if (events & BEV_EVENT_CONNECTED)
{
std::cout << "Connection server success." << std::endl;
}
return;
}
void libevent_client::input_cb(evutil_socket_t fd, short events, void *arg)
{
// 输入事件加入到未决事件集中后,激活了input_cb,此时event_cb还未被激活
bufferevent *bev = (bufferevent *)arg;
char buf[1024] = { 0 };
if (i > 0 && i < 11)
{
//std::cout << "input the message send to server: ";
//std::cin >> buf;
memcpy(buf, itoa(i, buf, _MAX_ITOSTR_BASE10_COUNT), 1);
}
if (strlen(buf))
{
bufferevent_write(bev, buf, strlen(buf) + 1);
}
memset(buf, 0, 1024);
Sleep(1000);
i++;
return;
}
void libevent_client::connect_communicate()
{
// 连接服务端
int res = bufferevent_socket_connect(m_bev, (struct sockaddr *)&m_sin, sizeof(m_sin));
if (res != 0)
{
std::cout << "connect failed." << std::endl;
return;
}
bufferevent_setcb(m_bev, read_cb, write_cb, event_cb, NULL);
bufferevent_enable(m_bev, EV_READ | EV_PERSIST);
m_event = event_new(m_base, bufferevent_getfd(m_bev), EV_WRITE | EV_PERSIST, input_cb, m_bev);
event_add(m_event, NULL);
int i = event_base_get_num_events(m_base, EVENT_BASE_COUNT_ADDED);
event_base_dispatch(m_base);
event_base_free(m_base);
event_free(m_event);
return;
}
.h文件
#pragma once
#include <event2/util.h>
class libevent_client
{
public:
libevent_client();
~libevent_client();
void initial(int port);
void connect_communicate();
private:
struct sockaddr_in m_sin;
int m_port;
evutil_socket_t m_fd;
struct event_base *m_base;
struct bufferevent *m_bev;
struct event *m_event;
private:
static void read_cb(struct bufferevent *bev, void *arg);
static void write_cb(struct bufferevent *bev, void *arg);
static void event_cb(struct bufferevent *bev, short events, void *arg);
static void input_cb(evutil_socket_t fd, short events, void *arg);
static int i;
};
在main函数中调用
#include <iostream>
#include "libevent_client.h"
int libevent_client::i = 0;
int main()
{
libevent_client client;
client.initial(6000);
client.connect_communicate();
}
同样的,如果是在同一台电脑客户端和服务端在两个VS项目中打开,将客户端的initial
接口中修改自己电脑的IP就可以运行。也可以将//m_sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
代码放开,然后注释下面代码运行也可以。如果是在两台电脑,就用下面inet_pton(AF_INET, "192.168.2.31", &m_sin.sin_addr.s_addr);
这个,把ip给改了就可以了。
记录
// 127.0.0.1表示本机
在libevent中没有写很多的注释,但是我感觉学习了libevent就能理解了。加油