linux服务器开发入门版入门版-socket小结
一、服务器/客户端构建流程
服务端函数调用流程:
socket->bind->listen->accept->recv/send->close
客户端函数调用流程:
socket->connect->send/recv->close
send/recv可以进行多次交互。
1.服务端在listen之前,客户端不能向服务端发起连接请求
2.服务端在调用listen之后,服务端的socket开始监听客户端的连接
3.客户端调用connect函数向服务端发起连接请求
4.在tcp底层,客户端和服务端握手后建立起通信通道,如果有多个客户端请求,在服务端会形成一个已经准备好的连接队列
5.服务端调用accept从队列获取一个已准备好的连接,函数返回一个新的socket与客户端通信,listen的socket只负责监听客户端的连接请求。
端口范围1024~65535,1024以下为系统保留,非root权限不可用
二、函数详解
2.1 Socket函数
Int socket(int domain, int type, int protocol);
domain:协议域,又称协议族(family)。常用的协议族有AF_INET、AF_INET6、AF_LOCAL(或称AF_UNIX,Unix域Socket)、AF_ROUTE等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。
type:指定socket类型。常用的socket类型有SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等。
流式socket(SOCK_STREAM)是一种面向连接的socket,针对于面向连接的TCP服务应用。
数据报式socket(SOCK_DGRAM)是一种无连接的socket,对应于无连接的UDP服务应用。
SOCK_RAW(原始套接字):基于链路层处理报文,可以操作ip头数据
protocol指定协议。常用协议有IPPROTO_TCP、IPPROTO_UDP、IPPROTO_STCP、IPPROTO_TIPC等,分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。如果为0,协议将自动选择。
返回值为fd,文件描述符。
2.2 bind
Int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
参数sockfd,需要绑定的socket,参数addr,存放了服务端用于通信的地址和端口,参数addrlen表示addr结构体的大小,返回值:成功则返回0,失败返回-1,错误原因存于errno 中。
bind 将通信所用的地址和端口绑定到socket上.
sockaddr 构建方法
sockaddr_in server_addr;
server_addr.sin_family=AF_INET;
server_addr.sin_addr.s_addr = inet_addr(“192.168.0.1”); //inet_addr将点分十进制的IP转换成一个长整数型数,若字符串有效则将字符串转换为32位二进制网络字节序的IPV4地址,否则为INADDR_NONE
server_addr.sin_port=htons(10000); //htons 将整数转换为网络字节序
2.3 listen
Int listen(int socketfd, int backlog)
返回:0-成功, -1-失败, 参数sockfd是已经被bind过的socket,
参数backlog,等待队列的最大长度,多个客户端同时请求时,请求队列的最大长度,不宜设置太大。
Listen将socket设为监听模式,开始接受客户端请求,把主动连接socket变为被动连接的socket,使得这个socket可以接受其它socket的连接请求,从而成为一个服务端的socket。
2.4 connect
Int connect(int sockfd, struct sockaddr *serv_addr,int addrlen);
将参数sockfd 的socket 连至参数serv_addr 指定的服务端,参数addrlen为sockaddr的结构长度
,返回值:成功则返回0,失败返回-1,错误原因存于errno 中。
Connect 向服务器发起连接请求,只用于客户端。
注:如果服务器关闭,connect函数会阻塞,如果不想程序阻塞,可用如下方法:
1.建立socket
2.将该socket设置为非阻塞模式
3.调用connect()
4.使用select()检查该socket描述符是否可写
5.根据select()返回的结果判断connect()结果
6.将socket设置为阻塞模式(如果你的程序不需要用阻塞模式,这步就省了,不过一般情况都是用阻塞模式,这样容易管理)
ioctl(sockfd, FIONBIO, &ul); //设置为非阻塞模式
if( connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1)
{
tm.tv_set = TIME_OUT_TIME;
tm.tv_uset = 0;
FD_ZERO(&set);
FD_SET(sockfd, &set);
if( select(sockfd+1, NULL, &set, NULL, &tm) > 0)
{
getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, (socklen_t *)&len);
if(error == 0)
ret = true;
else
ret = false;
}
}
else ret = false;
ioctl(sockfd, FIONBIO, &ul); //设置为阻塞模式
2.5 accept
int accept(int sockfd, struct sockaddr *addr,socklen_t *addrlem);
参数sockfd是已经被listen过的socket,参数addr用于存放客户端的地址信息,用sockaddr结构体表达,如果不需要客户端的地址,可以填0,参数addrlen用于存放addr参数的长度,如果addr为0,addrlen也填0。
Accept 从已准备好的连接队列获取一个请求,队列为空,accept函数将阻塞.
accept返回值为一个新的socket,获取的地址为互联网出口地址
如本机ip:本机IP: 171.83.7.19湖北省武汉市洪山区 电信,百度(查看ip地址)
局域网地址:192.168.233.128
2.6 recv函数
Size read(sockfd, buff, buff_size);
Size_t recv(int sockfd, void *buf,size_t len,int flags);
read和recv区别,2个函数都是接收数据,read比recv少一个参数,相当于flags为MSG_WAITALL,直到读取到buff_size 长度的数据才会返回,否则会阻塞。
sockfd为已建立好连接的socket,buf为用于接收数据的内存地址,可以是C语言基本数据类型变量的地址,也可以数组、结构体、字符串,只要是一块内存就行了。len需要接收数据的长度,不能超过buf的大小,否则内存溢出。
Flags 含义
Linux
MSG_WAITALL,阻塞读取,等待所有数据。
MSG_DONTROUTE 绕过路由表查找。
MSG_DONTWAIT 非阻塞读取。
MSG_PEEK 只查看数据,不把数据从接收缓冲区移除
recv函数用于接收对端socket发送过来的数据。
recv函数用于接收对端通过socket发送过来的数据。不论是客户端还是服务端,应用程序都用recv函数接收来自TCP连接的另一端发送过来数据,socket的对端没有发送数据,recv函数就会等待,如果对端发送了数据,函数返回接收到的字符数.
2.7 send函数
Ssize_t send(int sockfd, const void* buf,size_t len,inf flags);
sockfd为已建立好连接的socket
buf为需要发送的数据的内存地址,可以是C语言基本数据类型变量的地址,也可以数组、结构体、字符串,内存中有什么就发送什么,len需要发送的数据的长度,buf中有效数据的长度。
flags一般填0, 其他数值意义不大,函数返回已发送的字符数。出错时返回-1,错误信息errno被标记,是网络断开,或socket已被对端关闭,send函数不会立即报错,要过几秒才会报错,对一个对端已经关闭的socket调用两次write,第二次将会生成SIGPIPE信号, 该信号默认结束进程,可将flag设置为MSG_NOSIGNAL,避免因SIGPIPE导致程序崩溃。
send函数用于把数据通过socket发送给对端。不论是客户端还是服务端,应用程序都用send函数来向TCP连接的另一端发送数据
注:send只是将数据发送至本机的网络发送缓冲区中,不一定将数据发送到的远方,
可通过netstat -nat|grep port查看缓存区大小
也可在代码中,调用函数socket对应缓冲区大小,
int bufsize = 0;
ioctl(m_fd,TIOCOUTQ,&bufsize);
//查看接收缓冲区大小
ioctl(m_fd,FIONREAD,&bufsize);
附带简单tcpserver代码和modbus tcp client代码
///TCP Server
#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
constexpr int MAXLNE = 200;
int main(int argc, char **argv)
{
int listenfd, connfd, n;
struct sockaddr_in servaddr;
char buff[MAXLNE];
if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
printf("create socket error: %s(errno: %d)\n", strerror(errno), errno);
return 0;
}
int port = atoi(argv[1]);
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(port);
if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) {
printf("bind socket error: %s(errno: %d)\n", strerror(errno), errno);
return 0;
}
if (listen(listenfd, 10) == -1) {
printf("listen socket error: %s(errno: %d)\n", strerror(errno), errno);
return 0;
}
printf("========waiting for client's request========\n");
while (true) {
if ((connfd = accept(listenfd, (struct sockaddr *)nullptr, nullptr)) == -1) {
printf("accept socket error: %s(errno: %d)\n", strerror(errno), errno);
continue;
}
n = recv(connfd, buff, MAXLNE, 0);
buff[n] = '\0';
printf("recv msg from client: %s\n", buff);
sleep(1);
//close(connfd);
}
close(listenfd);
return 0;
}
//modbus tcp client
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <netinet/tcp.h>
#include<map>
using namespace std;
int request = 0;
static int DectoHex(int dec, char *hex, int length)
{
int i;
for (i = length - 1; i >= 0; i--)
{
hex[i] = (dec % 256) & 0xFF;
dec /= 256;
}
return 0;
}
void parseFuncode1(const char* data,int len,int beginaddr ,int recvlen)
{
int s = len / 8;
int pos = beginaddr;
for (size_t i = 0; i < s; i++)
{
for (size_t j = 0; j < 8; j++)
{
int value = data[i] & (1 << j);
printf("addr: %d,value :%d \n", pos, value);
pos ++;
}
}
printf("recv length:%d,requeset:%d \n", recvlen, request);
request++;
}
void Modbus_sender_fundoce1(int client_socket, int slaveid, int addr, int value)
{
unsigned char header[6] = { 0x19,0x00,0x00,0x00,0x00,0x06 };
unsigned char order[6] = { 0 };
order[0] = slaveid;
order[1] = 5;// funccode 05
order[2] = addr >> 8;
order[3] = addr & 0x00ff;
uint16_t data = value ? 0xFF00 : 0;
order[4] = data >> 8;
order[5] = data & 0x00ff;
int recvlen,send;
unsigned char request[12] = { 0 };
memcpy(request, header, sizeof(header));
memcpy(request + sizeof(header), order, sizeof(order));
send = write(client_socket,request,sizeof(request));
}
void floatToBytesLittle(float value, unsigned char* cSendBuff, int pos)
{
unsigned short i = 0;
float floatVariable = value;
unsigned char *pdata = (unsigned char *)&floatVariable;
cSendBuff[i+1+pos]= *pdata++;
cSendBuff[i+pos]= *pdata++;
cSendBuff[i+3+pos]= *pdata++;
cSendBuff[i+2+pos]= *pdata++;
/*for (i = 0; i < 4; i++)
{
cSendBuff[i+pos] = *pdata++;//float转BYTE
}*/
}
void Modbus_sender_fundoce3(int client_socket, int slaveid, int beginaddr, float value) //写一个寄存器,使用功能码16,修改后就可以写多个
{
unsigned char Temp_buf[20];
Temp_buf[0] = 0;
Temp_buf[1] = 0;
Temp_buf[2] = 0;
Temp_buf[3] = 0;
Temp_buf[4] = 0;
Temp_buf[5] = 11;//从ID开始到最后的字节数
Temp_buf[6] = slaveid;//从机ID
Temp_buf[7] = 16;//命令代码
Temp_buf[8] = (beginaddr-1)/256;//addr head //开始的地址
Temp_buf[9] = (beginaddr-1) % 256;
Temp_buf[10] = 0;//number of addr //地址的长度
Temp_buf[11] = 2;
Temp_buf[12] = 4;//# of Bytes for values //一共多少byte的值
value = 5.8;
floatToBytesLittle(value,Temp_buf,13);
int recvlen,send;
send=write(client_socket,Temp_buf,sizeof(Temp_buf));
}
void parseFuncode3_2(const char* data,int len,int beginaddr ,int recvlen)
{
int s = len * 8;
int pos = beginaddr;
map<int, uint16_t> dataMap;
int num = 0;
for (size_t i = 0; i < s;)
{
char tmp[2];
uint16_t regVal;
tmp[0] = data[i + 1];
tmp[1] = data[i];
memcpy(®Val, tmp, 2);
i += 2;
dataMap[num] = regVal;
num ++;
}
printf("recv length:%d,requeset:%d \n", recvlen, request);
request++;
}
void parseFuncode3(const char* data,int len,int beginaddr ,int recvlen)
{
int s = len * 8;
int pos = beginaddr;
map<int, uint16_t> dataMap;
int num = 0;
for (size_t i = 0; i < s;)
{
char tmp[2];
uint16_t regVal;
tmp[0] = data[i + 1];
tmp[1] = data[i];
memcpy(®Val, tmp, 2);
i += 2;
dataMap[num] = regVal;
num ++;
}
//if(dataMap.size() == 8)
{
uint64_t value1;
value1 = ((uint64_t)dataMap[3]<<48) + ((uint64_t)dataMap[2]<<32) + ((uint64_t)dataMap[1]<<16) + dataMap[0];
uint64_t value2;
value2 = ((uint64_t)dataMap[7]<<48) + ((uint64_t)dataMap[6]<<32) + ((uint64_t)dataMap[5]<<16) + dataMap[4];
double lat = value1 * 0.0000000000000001;
double lon = value2 * 0.0000000000000001;
printf("lat:%f,lon:%f \n", lat, lon);
}
printf("recv length:%d,requeset:%d \n", recvlen, request);
request++;
}
int modbusTCP_request(int client_socket,int slaveid,int funcode,int beginaddr, int length)
{
int modbus_head_len = 9; //3:rtu 9:tcp
int recvlen,send;
unsigned char header[6] = { 0x19,0x00,0x00,0x00,0x00,0x06 };
unsigned char order[6] = { 0 };
order[0] = slaveid; //slave_id
order[1] = funcode; //func_code
char len[2] = { 0 };
char begin_num[2] = { 0 }; //寄存器起始地址
DectoHex(length, len, 2);
DectoHex(beginaddr, begin_num, 2);
memcpy(order + 2, begin_num, sizeof(begin_num));
memcpy(order + 4, len, sizeof(len));
unsigned char request[12] = { 0 };
memcpy(request, header, sizeof(header));
memcpy(request + sizeof(header), order, sizeof(order));
char buff[1024] = {0};
while(1)
{
send=write(client_socket,request,sizeof(request));
if(send<0)
{
printf("send error\n");
return -1;
}
recvlen=recv(client_socket,buff,1024, 0);
if(recv>0)
{
printf("%d\n",recvlen);
if(funcode ==1)
{
parseFuncode1((buff + 9), length, beginaddr ,recvlen);
}
else if(funcode ==3)
{
parseFuncode3_2((buff + 9), length, beginaddr ,recvlen);
}
}
sleep(2);
}
return 0;
}
// argv 1 ip 2 port 3 slaceid 4 funcode 5 beginaddr 6 length
int main(int argc, char *argv[])
{
int s,ct;
struct sockaddr_in server_addr;
int err;
s=socket(AF_INET,SOCK_STREAM,0);
if(s<0)
{
printf("socket error\n");
return -1;
}
char *cport = argv[2];
int port = atoi(cport);
server_addr.sin_family=AF_INET;
char * ip = argv[1];
server_addr.sin_addr.s_addr = inet_addr(ip);
server_addr.sin_port=htons(port);
ct=connect(s,(struct sockaddr *)&server_addr,sizeof(struct sockaddr));
if(ct<0)
{
printf("connect error\n");
return -1;
}
int slaveid = atoi( argv[3]);
int funcode = atoi( argv[4]);
int beginaddr = atoi( argv[5]);
int length = atoi(argv[6]);
modbusTCP_request(s,slaveid, funcode,beginaddr, length);
close(s);
return 0;
}
2.8 ws和http
ws和http协议 自身会处理粘包 保证数据完整 不需要额外的粘包