1.套接字
套接字是用于在计算机网络中进行通信的一种技术。它是操作系统提供的一种接口,通过该接口,应用程序可以通过网络连接进行数据的传输和接收。
套接字包含了一个IP地址和一个端口号,用于唯一标识一个网络连接。通过套接字,应用程序可以使用不同的协议(如TCP、UDP)进行数据的传输。
2.socket
套接字(Socket)是一种编程接口,用于实现网络通信。它是操作系统提供的一种抽象,允许应用程序通过网络进行数据传输和接收。
在网络编程中,套接字通常被称为 Socket。通过 Socket,应用程序可以通过网络连接到其他计算机并与其进行通信。Socket 提供了一种标准的编程接口,使得应用程序可以使用不同的协议(如 TCP、UDP)进行数据传输。
1.创建socket
cpp
int socket(int domain, int type, int protocol);
- domain :指定地址家族,例如
AF_INET
或AF_INET6
。 - type :指定socket类型,例如
SOCK_STREAM
(用于TCP)或SOCK_DGRAM
(用于UDP)。 - protocol:通常设置为0,让系统选择type参数对应的默认协议。
2.绑定
cpp
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- sockfd:socket文件描述符。
- addr :指向包含地址信息的
sockaddr
结构的指针。 - addrlen :
sockaddr
结构的大小。
在Linux网络编程中,recvfrom
是一个系统调用,用于从(已连接的或未连接的)socket接收数据。对于未连接的socket(例如UDP socket),recvfrom
允许接收来自任何发送者的数据,并返回发送者的地址信息。
cpp
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
- sockfd:接收数据的socket文件描述符。
- buf:指向缓冲区的指针,该缓冲区用于存储接收到的数据。
- len:缓冲区的大小,即要接收的最大数据量。
- flags :指定调用行为的标志,例如
MSG_DONTWAIT
(非阻塞操作)或MSG_PEEK
(查看数据但不从缓冲区中移除)。 - src_addr :指向
sockaddr
结构的指针,该结构用于存储发送者的地址信息。如果不需要此信息,可以设置为NULL
。 - addrlen :指向
socklen_t
类型的指针,它初始化为src_addr
结构的大小。在调用返回时,addrlen
被设置为实际存储在src_addr
中的地址的大小。
sendto 是Linux网络编程中的一个系统调用,用于向一个指定的地址发送数据。这个系统调用通常用于UDP协议,因为UDP是无连接的,所以需要在每次发送数据时指定目标地址。对于TCP协议,由于它是面向连接的,所以通常使用 send
或 write
系统调用来发送数据。
cpp
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
cpp
#pragma once
#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "Log.hpp"
#include "InetAddr.hpp"
// echo server -> client -> server
enum
{
SOCKET_ERROR = 1,
BIND_ERROR,
USAGE_ERROR
};
const static int defaultfd = -1;
class UdpServer
{
public:
UdpServer(uint16_t port) : _sockfd(defaultfd), _port(port), _isrunning(false)
{
}
void InitServer()
{
// 1. 创建udp socket 套接字 --- 必须要做的
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (_sockfd < 0)
{
LOG(FATAL, "socket error, %s, %d\n", strerror(errno), errno);
exit(SOCKET_ERROR);
}
LOG(INFO, "socket create success, sockfd: %d\n", _sockfd);
// 2.0 填充sockaddr_in结构
struct sockaddr_in local; // struct sockaddr_in 系统提供的数据类型。local是变量,用户栈上开辟空间。int a = 100; a = 20;
bzero(&local, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(_port); // port要经过网络传输给对面,先到网络,_port:主机序列-> 主机序列,转成网络序列
// a. 字符串风格的点分十进制的IP地址转成 4 字节IP
// b. 主机序列,转成网络序列
// in_addr_t inet_addr(const char *cp) -> 同时完成 a & b
// local.sin_addr.s_addr = inet_addr(_ip.c_str()); // "192.168.3.1" -> 字符串风格的点分十进制的IP地址 -> 4字节IP
local.sin_addr.s_addr = INADDR_ANY; // htonl(INADDR_ANY);
// 2.1 bind sockfd和网络信息(IP(?) + Port)
int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
if (n < 0)
{
LOG(FATAL, "bind error, %s, %d\n", strerror(errno), errno);
exit(BIND_ERROR);
}
LOG(INFO, "socket bind success\n");
}
void Start()
{
// 一直运行,直到管理者不想运行了, 服务器都是死循环
// UDP是面向数据报的协议
_isrunning = true;
while (true)
{
char buffer[1024];
struct sockaddr_in peer;
socklen_t len = sizeof(peer); // 必须初始化成为sizeof(peer)
// 1. 我们要让server先收数据
ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
if (n > 0)
{
buffer[n] = 0;
InetAddr addr(peer);
LOG(DEBUG, "get message from [%s:%d]: %s\n", addr.Ip().c_str(), addr.Port(), buffer);
// 2. 我们要将server收到的数据,发回给对方
sendto(_sockfd, buffer, strlen(buffer), 0, (struct sockaddr *)&peer, len);
}
}
_isrunning = false;
}
~UdpServer()
{
}
private:
int _sockfd;
// std::string _ip; // 暂时这样写,这个地方不是必须的.TODO
uint16_t _port; // 服务器所用的端口号
bool _isrunning;
};
cpp
#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
void Usage(std::string proc)
{
std::cout << "Usage:\n\t" << proc << " serverip serverport\n"
<< std::endl;
}
// ./udpclient serverip serverport
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage(argv[0]);
exit(1);
}
std::string serverip = argv[1];
uint16_t serverport = std::stoi(argv[2]);
// 1. 创建socket
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd < 0)
{
std::cerr << "socket error" << std::endl;
}
// 2. client要不要bind?一定要,client也要有自己的IP和PORT。要不要显式[和server一样用bind函数]的bind?不能!不建议!!
// a. 如何bind呢?udp client首次发送数据的时候,OS会自己自动随机的给client进行bind --- 为什么?防止client port冲突。要bind,必然要和port关联!
// b. 什么时候bind呢?首次发送数据的时候
// 构建目标主机的socket信息
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(serverport);
server.sin_addr.s_addr = inet_addr(serverip.c_str());
std::string message;
// 2. 直接通信即可
while(true)
{
std::cout << "Please Enter# ";
std::getline(std::cin, message);
sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr*)&server, sizeof(server));
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
char buffer[1024];
ssize_t n = recvfrom(sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&peer, &len);
if(n > 0)
{
buffer[n] = 0;
std::cout << "server echo# " << buffer << std::endl;
}
}
return 0;
}
cpp
#include <iostream>
#include <memory>
#include "UdpServer.hpp"
void Usage(std::string proc)
{
std::cout << "Usage:\n\t" << proc << " local_port\n" << std::endl;
}
// ./udpserver port
// 云服务器的port默认都是禁止访问的。云服务器放开端口8080 ~ 8085
int main(int argc, char *argv[])
{
if(argc != 2)
{
Usage(argv[0]);
exit(USAGE_ERROR);
}
EnableScreen();
// std::string ip = argv[1]; // TODO
uint16_t port = std::stoi(argv[1]);
std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port); // C++14
usvr->InitServer();
usvr->Start();
return 0;
}