CMakeLists文件:
cmake_minimum_required(VERSION 3.19)
project(CS)
set(CMAKE_CXX_STANDARD 11)
include_directories(./include)
link_libraries(pthread)
add_executable(client client.cpp)
add_executable(server server.cpp)
socket函数:
SYNOPSIS
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
失败返回-1
ARGUMENTS:
domain:IP层协议
AF_LOCAL AF_UNIX AF_INET AF_INET6
type:传输层协议
SOCK_STREAM SOCK_DGRAM SOCK_SEQPACKET SOCK_RAW SOCK_RDM SOCK_PACKET
protocol:可写为0,根据type参数自动决定
客户端调用connect连接:连接错误返回-1
NAME
connect - initiate a connection on a socket
SYNOPSIS
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
#include <netinet/in.h>
#include <unistd.h>
#include <cstring>
#include <arpa/inet.h>
char ip[] = "127.0.0.1";
int port = 9999;
struct sockaddr_in sockaddr;
memset(&sockaddr, 0, sizeof(sockaddr));
sockaddr.sin_family = AF_INET;
sockaddr.sin_addr.s_addr = inet_addr(ip);
sockaddr.sin_port = htons(port);
if(connect(csockfd, (struct sockaddr*)&sockaddr, sizeof(sockaddr)) < 0){
perror("connect");
close(csockfd);
return 1;
}
发送与接收消息:recv与send函数在相应的缓冲区为空或满时阻塞
NAME
send, sendto, sendmsg - 从套接字发送消息
概述
#include <sys/types.h>
#include <sys/socket.h>
int send(int s, const void *msg, size_t len, int flags);
int sendto(int s, const void *msg, size_t len, int flags, const struct
sockaddr *to, socklen_t tolen); //用于UDP通信
int sendmsg(int s, const struct msghdr *msg, int flags);
参数 flags 是一个标志字,可以包含下列标志:
对于支持带外数据的套接字,
MSG_OOB 将送出 out-of-band (带外)数据(比如, SOCK_STREAM
类型的套接字); 下层协议也必须支持. 带外 数据.
MSG_DONTROUTE
在送出分组时不使用网关.只有直接连接在网络上的主机
才能接收到数据.这个标志通常仅用于诊断和路由程序.
可路由的协议族才能使用这个标志;包套接字不可以.
MSG_DONTWAIT
使用非阻塞式操作;如果操作需要阻塞,将返回 EAGAIN 错误(也可以用
F_SETFL fcntl(2) 设置 O_NONBLOCK 实现这个功能.)
MSG_DONTWAIT
使用非阻塞式操作;如果操作需要阻塞,将返回 EAGAIN 错误(也可以用
F_SETFL fcntl(2) 设置 O_NONBLOCK 实现这个功能.)
MSG_NOSIGNAL
当流式套接字的另一端中断连接时不发送 SIGPIPE 信号,但仍然返回
EPIPE 错误.
MSG_CONFIRM (仅用于Linux 2.3以上版本)
通知链路层发生了转发过程:得到了另一端的成功应答.
如果链路层没有收到通知,它将按照常规探测网络上的相邻
主机(比如通过免费arp). 只能用于 SOCK_DGRAM 和 SOCK_RAW
类型的套接字,且仅对IPv4和IPv6有效.详情参见 arp(7)
结构体 msghdr 的定义如下.详情参见 recv(2) 和下文.
struct msghdr {
void * msg_name; /*地址选项*/
socklen_t msg_namelen; /*地址长度*/
struct iovec * msg_iov; /*消息数组*/
size_t msg_iovlen; /*msg_iov中的元素个数*/
void * msg_control; /*辅助信息,见下文*/
socklen_t msg_controllen; /*辅助数据缓冲区长度*/
int msg_flags; /*接收消息标志*/
};
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
服务器端绑定并监听:
NAME 名称
listen - listen for connections on a socket 在一个套接字上倾听连接
SYNOPSIS 概述
#include <sys/socket.h>
int listen(int s, int backlog);
backlog参数为一次性可以接收的连接数,最大为128
函数执行成功时返回0.错误时返回-1,并置相应错误代码.
if(::bind(ssockfd, (struct sockaddr*)&sockaddr, sizeof(sockaddr)) < 0){
perror("bind");
close(ssockfd);
return 1;
}
if(listen(ssockfd, 128) < 0){
perror("listen");
close(ssockfd);
return 1;
}
accept函数接收连接,成功返回用于通信的fd,失败返回-1
while(true){
int cfd = accept(ssockfd, nullptr, nullptr);
if(cfd < 0){
perror("accept");
close(cfd);
close(ssockfd);
return 1;
}
char recvBuff[1024] = {0};
int len = recv(cfd, recvBuff, sizeof(recvBuff), 0);
if(len < 0){
perror("recv");
close(cfd);
close(ssockfd);
return 1;
}
cout << "recieved from client:" << recvBuff << endl;
char sendBuff[1024] = "I recieved your message";
len = send(cfd, sendBuff, sizeof(sendBuff), 0);
if(len < 0){
perror("send");
close(cfd);
close(ssockfd);
return 1;
}
close(cfd);
}
字节序:
小端:主机字节序,低位字节存储在低地址位
大端:网络字节序,低位字节存储在高地址位
转换:#include<arpa/inet.h>
短整型从主机字节序转为网络字节序:uint16_t htons(uint16_t hostshort)
整型从主机字节序转为网络字节序:uint32_t htonl(uint32_t hostshort)
反过来:uint16_t ntohs(uint16_t netlong) 转端口
uint32_t ntohl(uint32_t netlong) 转ip地址
主机字节序的ip地址转换为网络字节序:
int inet_pton(int af, const char* src, void* dst)
af:AF_INET,AF_INET6
src:要转换的ip字符串
dst:整型变量的地址,用来存储转换的大端的整型数
成功返回1,返回0则ip字符串有问题,返回-1表示af参数不对
反过来
const char* inet_ntop(int af,const char* src,void* dst, socket_t size)
size是dst对应内存的大小
函数调用成功,返回dst的值,调用失败返回空指针
将点分十进制ip转换为大端整型:只能进行IPV4
in_addr_t inet_addr(const char* cp)
反过来char* inet_ntoa(struct in_addr_in)
客户端根据域名/主机名/字符串IP获取大端序IP:
struct hostent* gethostbyname(const char* name);
struct hostent{
char* h_name; //主机名
char** h_aliases; //主机所有的别名
short h_addrtype; //AF_INET、AF_INET6
short h_length; //主机IP地址长度
char** h_addr_list //主机IP地址
}
memcpy(&sockaddr,sin_addr,h->h_addr,h->h_length);
通信时序:
三次握手:
- 客户端调用connect函数,发出SYN请求,服务端调用accept函数接收请求
- 服务端发送SYN+ACK,客户端connect函数返回
- 客户端发送ACK,服务端accept函数返回
数据传输:
write函数发送数据,read函数返回后发送ACK,若返回为0,则表示接收缓冲区为空
四次挥手:
- 客户端调用close函数请求关闭连接,发送FIN
- 服务端收到后发送ACK
- 服务端read返回0时调用close,发送FIN+ACK
- 客户端close返回,发送ACK,服务端收到后close返回