什么是Socket
在计算机通信领域,socket 被翻译为“套接字”,它是计算机之间进行通信的一种约定或一种方式。通过 socket 这种约定,一台计算机可以接收其他计算机的数据,也可以向其他计算机发送数据
socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。
我的理解就是Socket就是该模式的一个实现:即socket是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)。
Socket()函数返回一个整型的Socket描述符,随后的连接建立、数据传输等操作都是通过该Socket实现的。
Socket基础
首先看一个基于TCP的CS案例,客户端连接到服务器后,收到服务器发来的消息并打印,为步骤清晰,去掉了一些差错判断。
Server
#include <iostream>
#include <sys/socket.h> // int socket(int, int, int);
#include <netinet/in.h> // IPPROTO_TCP
#include <arpa/inet.h> // in_addr_t inet_addr(const char *);
#include <unistd.h> //write and read
int setAddr(sockaddr_in * addr,short port,const char * ip){
memset(addr, 0, sizeof(sockaddr_in)); //每个字节都用0填充
addr->sin_family = AF_INET; //使用IPv4地址
addr->sin_addr.s_addr = inet_addr(ip); //具体的IP地址
addr->sin_port = htons(port); //端口,使用htons转换为网络字节序
return 0;
}
int main() {
// 配置地址:ip port 协议
sockaddr_in addr{};
setAddr(&addr,1234,"127.0.0.1");
// 创建socket
int fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// 绑定socket和addr
if(0!=bind(fd, (struct sockaddr *) &addr, sizeof(addr))){
std::cout << "bind failed!" << std::endl;
}
// 监听指定地址端口
if(0!=listen(fd, 20)){
std::cout << "listen failed!" << std::endl;
}
//接收连接请求
sockaddr_in clientAddr{};
socklen_t clientAddrLen = sizeof(clientAddr);
int clientFd = accept(fd, (struct sockaddr*)&clientAddr, &clientAddrLen);
//向客户端发送数据
char str[] = "Welcome!";
write(clientFd, str, sizeof(str));
//关闭套接字
close(clientFd);
close(fd);
return 0;
}
Client
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
int setAddr(sockaddr_in * addr,short port,const char * ip){
memset(addr, 0, sizeof(sockaddr_in)); //每个字节都用0填充
addr->sin_family = AF_INET; //使用IPv4地址
addr->sin_addr.s_addr = inet_addr(ip); //具体的IP地址
addr->sin_port = htons(port); //端口
return 0;
}
int main() {
// 地址
sockaddr_in addr{};
setAddr(&addr,1234,"127.0.0.1");
//1创建socket
int fd = socket(AF_INET, SOCK_STREAM, 0);
//2连接到socket
if(0!=connect(fd,(struct sockaddr*)&addr,sizeof(addr) ) ){
std::cout<<"connect failed!"<<std::endl;
}
//3读取服务器传回的数据
char buffer[40];
read(fd, buffer, sizeof(buffer)-1);
printf("%s\n", buffer);
//4关闭
close(fd);
return 0;
}
相关结构
typedef unsigned char __uint8_t;
typedef __uint8_t sa_family_t;
typedef __uint16_t in_port_t;
typedef __uint32_t in_addr_t;
struct sockaddr {
__uint8_t sa_len;
sa_family_t sa_family;//__uint8_t
char sa_data[14];
};
struct in_addr {
in_addr_t s_addr;//__uint32_t
};
struct sockaddr_in {
__uint8_t sin_len; //1byte
sa_family_t sin_family; //1byte
in_port_t sin_port; //2byte
struct in_addr sin_addr; //4byte
char sin_zero[8];//8byte
};
相关函数
socket
/*
* @file #include <sys/socket.h>
* @brief create an endpoint for communication
* @param domain
* PF_LOCAL(AF_UNIX) Host-internal protocols, formerly called PF_UNIX,
* PF_UNIX(AF_UNIX) Host-internal protocols, deprecated, use PF_LOCAL,
* PF_INET(AF_INET) Internet version 4 protocols,
* PF_ROUTE(AF_ROUTE) Internal Routing protocol,
* PF_KEY(pseudo_AF_KEY) Internal key-management function,
* PF_INET6(AF_INET6) Internet version 6 protocols,
* PF_SYSTEM(AF_SYSTEM) System domain,
* PF_NDRV(AF_NDRV) Raw access to network device,
* PF_VSOCK(AF_VSOCK) VM Sockets protocols
*
* @param type
* SOCK_STREAM stream socket
* SOCK_DGRAM datagram socket
* SOCK_RAW raw-protocol interface
*
* @param protocol usrally 0
* IPPROTO_TCP
* IPPROTO_UDP
* ... more in netinet/in.h
*
* @return -1 if an error occurs, otherwise the return value is a descriptor referencing the socket.
*
* ERRORS
The socket() system call fails if:
[EACCES] Permission to create a socket of the specified type and/or protocol is denied.
[EAFNOSUPPORT] The specified address family is not supported.
[EMFILE] The per-process descriptor table is full.
[ENFILE] The system file table is full.
[ENOBUFS] Insufficient buffer space is available. The socket
cannot be created until sufficient resources are freed.
[ENOMEM] Insufficient memory was available to fulfill the request.
[EPROTONOSUPPORT] The protocol type or the specified protocol is not supported within this domain.
[EPROTOTYPE] The socket type is not supported by the protocol.
If a new protocol family is defined, the socreate process is free to return any desired error code.
The socket() system call will pass this error code along (even if it is undefined).
*/
int socket(int domain, int type, int protocol);
bind
/*
* @file #include <sys/socket.h>
* @brief bind a file descriptor with sockaddr
* @param socket socket file descriptor
* @param addr point of addr struct
* @param socklen_t size of addr
*/
int bind(int socket, const struct sockaddr * addr, socklen_t)
listen
/*
* @file #include <sys/socket.h>
* @brief bind a file descriptor with sockaddr
* @param socket socket file descriptor
* @param backlog maximum length for the queue of pending connections
* @return value 0 if successful; otherwise the value -1
*/
int listen(int socket, int backlog);
accept
/*
* @file #include <sys/socket.h>
* @brief bind a file descriptor with sockaddr
* @param socket socket file descriptor
* @param restrict_address maximum length for the queue of pending connections
* @param restrict_address_len maximum length for the queue of pending connections
* @return -1 on error,a non-negative integer that is a descriptor for the accepted socket if it succeeds.
*/
int accept(int socket, struct sockaddr *restrict_address, socklen_t *restrict_address_len);
read & write
/*
* @file #include <unistd.h>
* @brief read or write data from fd to buffer
*/
ssize_t read(int fd, void * buffer, size_t buffer_size) ;
ssize_t write(int fd, const void * buffer, size_t buffer_size)
close
/*
* @file #include <unistd.h>
* @brief close a opened fd
*/
int close(int fd);
recv & send
上述例子中使用了read和write函数对socket进行读取和写入数据,事实上,操作socket的数据有更好的方式,即使用recv函数和send函数。
send
#include <sys/socket.h>
/*
* @brief send is used to transmit a message to another socket.
* @param socket socket file descriptor
* @param buffer message buffer
* @param length message bytes
* @param flags usrally 0。
The flags parameter may include one or more of the following:
#define MSG_OOB 0x1 // process out-of-band data
#define MSG_DONTROUTE 0x4 // bypass routing, use direct interface, usually used only by diagnostic or routing
programs.
* @return:
Upon successful completion, the number of bytes which were sent is returned.
Otherwise, -1 is returned and the global variable errno is set to indicate the error.
*/
ssize_t send(int socket, const void *buffer, size_t length, int flags);
recv
#include <sys/socket.h>
/*
* @brief recv receive messages from a socket, and may be used to receive data on
a socket whether or not it is connection-oriented.
* @param socket socket file descriptor
* @param buffer message buffer
* @param length message bytes
* @param flags usrally 0。
The flags parameter may include one or more of the following:
MSG_OOB process out-of-band data
MSG_PEEK peek at incoming message
MSG_WAITALL wait for full request or error
* @return:
Return the number of bytes received, or -1 if an error occurred.
For TCP sockets, the return value 0 means the peer has closed its half side of the connection.
*/
ssize_t recv(int socket, void *buffer, size_t length, int flags);
除此之外,还有
ssize_t sendmsg(int socket, const struct msghdr *message, int flags);
ssize_t recvmsg(int socket, struct msghdr *message, int flags);
ssize_t sendto(int socket, const void *buffer, size_t length, int flags,
const struct sockaddr *dest_addr, socklen_t dest_len);
ssize_t recvfrom(int socket, void *restrict buffer, size_t length, int flags,
struct sockaddr *restrict address, socklen_t *restrict address_len);
关于read/write,recv/send,recvmsg/sendmsg,recvfrom/sendto的区别
- read/write:通用文件描述符操作函数
- send:发送消息,send只可用于基于连接的套接字,send 和 write唯一的不同点是标志的存在,当标志为0时,send等同于write。
- sendto 和 sendmsg:既可用于无连接的套接字,也可用于基于连接的套接字;除了套接字设置为非阻塞模式,调用将会阻塞直到数据被发送完。
- recv:读取消息,一般只用在面向连接的套接字。
- recvfrom和recvmsg:可同时应用于面向连接的和无连接的套接字。
使用示例参考后文。
Example - Tcp Echo Server
基于有连接的网络通信,使用send和recv进行数据的收发
recv
char buffer[4096];
ssize_t bytes = recv(cfd, (void *)&buffer, 4096, 0);
send
char buffer[4096]{"test!"};
send(client_fd, &buffer, strlen(buffer), 0)
Server
#include <iostream>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
int main(int , char **) {
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) {
std::cerr << "Socket creat failed!"<<std::endl;
return -2;
}
sockaddr_in server_addr{};
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(1234);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
if (bind(fd, (sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
std::cerr <<"bind error."<<std::endl;
return -3;
}
if (listen(fd, 20) < 0) {
std::cerr << "listen error."<<std::endl;
return -4;
}
sockaddr_in client_addr{};
socklen_t client_addr_size = sizeof(client_addr);
int cfd = accept(fd, (sockaddr*)&client_addr, &client_addr_size);
if (cfd < 0) {
std::cerr << "accept error"<<std::endl;
return -5;
}
close(fd);//关闭监听的socket
char buffer[4096];
ssize_t bytes;
while (true) {
bytes = recv(cfd, (void *)&buffer, 4096, 0);
if (bytes == 0) {
std::cout << "client disconnected."<<std::endl;
break;
}
else if (bytes < 0) {
std::cerr << "recv error."<<std::endl;
break;
}
else {
std::cout << "[client]" << std::string(buffer, 0, bytes) << std::endl;
if (send(cfd, &buffer, bytes, 0) < 0) {
std::cerr << "send error, exiting...\n";
break;
}
}
}
close(cfd);
return 0;
}
Client
#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
int main(int , char **) {
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) {
std::cerr << "Socket creat error."<<std::endl;
return -2;
}
sockaddr_in server_addr{};
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(1234);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
if (connect(fd, (sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
std::cerr << "connect error."<<std::endl;
return -3;
}
char buf[4096];
std::string inputTemp;
while(true) {
std::memset(buf, 0, 4096);
std::cout << ">";
std::getline(std::cin, inputTemp, '\n');
std::strcpy(buf, inputTemp.c_str());
if (strlen(buf)==0) {
continue;
}
else if (strcmp(buf, "quit")==0) {
break;
}
ssize_t bytes_send = send(fd, &buf, (size_t)strlen(buf), 0);
if (bytes_send < 0) {
std::cerr << "send error."<<std::endl;
break;
}
ssize_t bytes_recv = recv(fd, &buf, 4096, 0);
if (bytes_recv < 0) {
std::cerr << "recv error."<<std::endl;
}
else if (bytes_recv == 0) {
std::cout << "server closed.";
}
else {
std::cout << "[server]" << std::string(buf, 0, bytes_recv) << std::endl;
}
}
close(fd);
return 0;
}
Example - Udp Echo Server
基于无连接的网络通信,使用sendto和recvfrom收发消息
sendto和recvfrom也可以支持面向连接的网络通信
recvfrom
ssize_t bytes_len = recvfrom(server_fd, buf, sizeof(buf), 0, (struct sockaddr *)&client_addr, &socklen);
sendto
ssize_t bytes_len = sendto(server_fd, buf, sizeof(buf), 0, (struct sockaddr *)&client_addr, socklen);
Server
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
int main()
{
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
addr.sin_port = htons(1234);
int fd = socket(AF_INET, SOCK_DGRAM, 0);//SOCK_DGRAM=>udp
if (fd < 0){
return -1;
}
if (bind(fd, (const sockaddr *)&addr, sizeof(addr))<0){
return -2;
}
char buf[1024];
sockaddr_in client_addr{};
socklen_t socklen = sizeof(client_addr);
while (true)
{
ssize_t bytes_len = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&client_addr, &socklen);
if (bytes_len < 0){
std::cout<<"recv failed"<<std::endl;
break;
}
else{
std::cout<<"[client]"<<buf<<std::endl;
}
bytes_len = sendto(fd, buf, sizeof(buf), 0, (struct sockaddr *)&client_addr, socklen);
if (bytes_len < 0){
std::cout<<"send failed"<<std::endl;
break;
}
}
close(fd);
return 0;
}
Client
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
int main()
{
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
addr.sin_port = htons(1234);
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if(fd < 0){
return -1;
}
ssize_t bytes_len ;
char buf[1024] = "hello world!";
bytes_len = sendto(fd, buf, strlen(buf), 0, (sockaddr*)&addr, sizeof(addr));
if(bytes_len < 0){
std::cout<<"send failed"<<std::endl;
return -2;
}
bytes_len = recvfrom(fd, buf, strlen(buf), 0,nullptr, nullptr);
if(bytes_len < 0){
std::cout<<"recv failed"<<std::endl;
return -3;
}
else{
std::cout<<"[server]"<<buf<<std::endl;
}
close(fd);
return 0;
}
Example - Sendmsg and Recvmsg
基于无连接的网络通信,使用sendto和recvfrom收发消息
sendto和recvfrom也可以支持面向连接的网络通信
首先回顾一下recvmsg和sendmsg的函数原型
struct iovec {
void * iov_base; /* [XSI] Base address of I/O memory region */
size_t iov_len; /* [XSI] Size of region iov_base points to */
};
struct msghdr {
void *msg_name; /* [XSI] optional address */
socklen_t msg_namelen; /* [XSI] size of address */
struct iovec *msg_iov; /* [XSI] scatter/gather array */
int msg_iovlen; /* [XSI] # elements in msg_iov */
void *msg_control; /* [XSI] ancillary data, see below */
socklen_t msg_controllen; /* [XSI] ancillary data buffer len */
int msg_flags; /* [XSI] flags on received message */
};
ssize_t sendmsg(int socket, const struct msghdr *message, int flags);
ssize_t recvmsg(int socket, struct msghdr *message, int flags);
Basic Usage
详细参考:https://blog.csdn.net/weixin_45309916/article/details/107905942
char buf[1024];
sockaddr_in client_addr{};
struct iovec msg_iov[1];
msg_iov[0].iov_base = buf;
msg_iov[0].iov_len = sizeof(buf);
struct msghdr msg{};
msg.msg_name = &client_addr;
msg.msg_namelen = sizeof(client_addr);
msg.msg_iov = msg_iov;
msg.msg_iovlen = 1;
msg.msg_control = nullptr;
msg.msg_controllen = 0;
msg.msg_flags = 0;
ssize_t bytes_len;
bytes_len = sendmsg(fd, &msg, 0);
bytes_len = recvmsg(fd, &msg, 0);
Server
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
int main()
{
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
addr.sin_port = htons(1234);
int fd = socket(AF_INET, SOCK_DGRAM, 0);//SOCK_DGRAM=>udp
if (fd < 0){
return -1;
}
if (bind(fd, (const sockaddr *)&addr, sizeof(addr))<0){
return -2;
}
char buf[1024];
sockaddr_in client_addr{};
iovec msg_iov[1];
msg_iov[0].iov_base = buf;
msg_iov[0].iov_len = sizeof(buf);
msghdr msg{};
msg.msg_name = &client_addr;
msg.msg_namelen = sizeof(client_addr);
msg.msg_iov = msg_iov;
msg.msg_iovlen = 1;
msg.msg_control = nullptr;
msg.msg_controllen = 0;
msg.msg_flags = 0;
while (true)
{
ssize_t bytes_len = recvmsg(fd,&msg,0);
if(bytes_len < 0){
std::cout<<"recv failed"<<std::endl;
break;
}
else{
std::cout<<"[client]"<<buf<<std::endl;
}
//change msg
memset(buf,0,sizeof(buf));
strcpy(buf,"Hi,Im server!");
bytes_len = sendmsg(fd, &msg, 0);
if (bytes_len < 0){
std::cout<<"send failed"<<std::endl;
break;
}
}
close(fd);
return 0;
}
Client
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
int main()
{
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
addr.sin_port = htons(1234);
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if(fd < 0){
return -1;
}
ssize_t bytes_len ;
char buf[1024] = "hello world!";
iovec msg_iov{};
msg_iov.iov_base = buf;
msg_iov.iov_len = sizeof(buf);
msghdr msg{};
msg.msg_name = &addr;
msg.msg_namelen = sizeof(addr);
msg.msg_iov = &msg_iov;
msg.msg_iovlen = 1;
msg.msg_control = nullptr;
msg.msg_controllen = 0;
msg.msg_flags = 0;
bytes_len = sendmsg(fd, &msg, 0);
if(bytes_len < 0){
std::cout<<"send failed"<<std::endl;
return -2;
}
bytes_len = recvmsg(fd,&msg,0);
if(bytes_len < 0){
std::cout<<"recv failed"<<std::endl;
return -3;
}
else{
std::cout<<"[server]"<<buf<<std::endl;
}
close(fd);
return 0;
}
标签:addr,int,fd,初探,msg,include,Socket,socket
From: https://www.cnblogs.com/pengpengda/p/18163647