linux服务器开发-socket小结

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(&regVal, 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(&regVal, 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协议 自身会处理粘包 保证数据完整 不需要额外的粘包
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值