当前位置: 代码迷 >> 综合 >> Linux服务器之Socket套接字基础+简单实例(网络信息结构体、大小端转换函数、SOCKET() 、绑定 BIND()、LISTEN()、ACCEPT())
  详细解决方案

Linux服务器之Socket套接字基础+简单实例(网络信息结构体、大小端转换函数、SOCKET() 、绑定 BIND()、LISTEN()、ACCEPT())

热度:75   发布时间:2024-01-09 07:13:15.0

一、基本概述
1、Socket套接字是系统提供用于网络应用开发的一系列Api接口 , 各个平台语言都有对Socket套接字的支持(兼容性跨平台能力强)。
2、Linux系统下 everything its file ,所以将网络设备抽象成了文件,可以通过文件处理的方式操作访问socket进行网络通信。linux下将socket包裹成文件描述符 sockfd,它跟传统的文件描述符fd不同。传统的文件描述符fd指向磁盘io ,向磁盘中读写数据。sockfd指向网络io 向网络中读写,访问网络设备。只是借用文件描述符特性,与磁盘没有任何关系。
二、基础信息及函数
1、网络信息结构体 (就是socket里面的东西)
1)可以通过自定义网络信息替换socket中的地址信息
2)双方进行网络通信,可以通过将对方的网络信息传出

struct sockaddr_in addr; //网络信息结构体(存储的都是大端序,网络字节序)
addr.sin_family = AF_INET|AF_INET6; //指定ip协议版本
addr.sin_addr.s_addr = 大端序IP地址(存储ip地址)
addr.sin_port = 大端序端口(存储端口)

2、大小端转换函数
大端序/网络字节序:低地址存储高字节,高地址存储低字节。
小端序/主机字节序:低地址存储低字节,高地址存储高字节。

#include <arpa/inet.h>

h表示host 主机 = 小端
n表示net 网络 = 大端
l = ip(long类型32位,IP就是32位)
s = port(short类型16位,端口就是16位)

大端序ip = htonl(小端序ip)
大端序端口 = htons(小端序port)
小端序ip = ntohl(大端序ip)
小端序port = ntohs(大端序端口)

但在操作系统中IP的表现形式就是一个32位数据,我们平常里看到的ip例如192.168.100.221 这种表现形式点分十进制。是为了我们方便观察,但是IP真正的存储就是一个32位的数据 ,我们上面的需要的传入的ip都是32位数据。但我们只知道小端字符串形式例如 192.168.11.164 ,如何转换成32位数据,然后再转换成大端?
下面这两个函数不用关心字符串是如何变成32位小端再转换成大端的 可以直接将字符串(大端)转成大端(字符串)。
1)字符串ip转大端序ip

inet_pton(AF_INET,"192.168.11.145",void * ptr); 
//ptr:转后的大端序ip存到哪里 一般都会存到网络信息结构体里面大端序ip的地方

2)大端序ip转字符串ip

inet_ntop(AF_INET,void * ptr, char * iparray , size_t ipsize ) 
//ptr:要转的大端序ip在哪,iparray:字符串ip传出到哪里,ipsize:传出到的数组的大小

这两个函数返回值都是转换后的对应的大端ip或者字符串ip。

3、SOCKET() 创建socketfd

#include <sys/socket.h>
int sockfd = socket(AF_INET , SOCK_STREAM , 0);

argv1 = 指定IP协议版本 , ipv4 or ipv6 AF_INET(ipv4) AF_INET6(ipv6)。
argv2 = 指定传输层协议, SOCK_STREAM(流式协议) SOCK_DGRAM(报文)。
argv3 = protocal = 0 ,表示默认。写STREAM默认协议为TCP , DGRAM默认协议为UDP。

注意:
1)第二个参数只能确定使用的协议的形式(流式或者报文,tcp和udp只是他们其中的一种,并非流式(报文)协议就是指tcp(udp))。
2)第三个参数具体确定了使用哪种中的哪个协议。

返回值:成功返回Sockfd , 失败返回-1并且ERRNO被设置

4、绑定 BIND 设置socket网络信息
利用SOCKET()函数创建出来的 sockfd里面有默认的ip(本机任意ip)和端口(随机端口)。我们如果需要设置满足需求的ip和端口,定制sockfd中的网络信息,就需要使用bind进行设置。
用自定义网络信息替换socket中的默认网络信息

struct sockaddr_in; //先定义一个网络信息结构体
int reval = bind(int sockfd , struct sockaddr * addr , sizeof(addr));

注意:要传sockaddr类型,而不是新的sockaddr_in addr类型,我们定义新的,传参强转。(历史遗留问题,需要我们向前兼容)
返回值,成功返回0,失败返回-1,并且ERRNO被设置
经典的C/S架构中, 通常Server需要绑定设置 , Client是否需要绑定看需求。

5、LISTEN监听网络(连接)事件(客户端一般不用,因为只有服务端需要监听网络事件,udp不用,tcp用)

listen(int sockfd , 128);//监听的socket与监听序列号

返回值:成功返回0 失败返回-1并且设置ERRNO

6、ACCEPT 服务端等待建立连接(阻塞函数)

int clientfd = accept(int serverfd , struct sockaddr * clientaddr , socklen_t* size);

argv1 = 阻塞在特定sockfd等待连接
argv2 = 连接成功传出对方的网络信息结构体
argv3 = 传入预存储网络信息结构体大小(我们传入能接受多大的),传出实际网络信息结构体大小(内核修改成实际的)
返回值:成功返回客户端的sockfd , 失败返回-1,并且设置ERRNO

7、CONNECT 客户端主动请求建立连接

connect(int clientfd , struct sockaddr * serveraddr , sizeof(serveraddr));

argv1 = 请求端的sockfd
argv2 = 对端(服务端)的网络信息结构体(主动跟谁请求)
argv3 = 网络信息结构体大小
返回值:成功返回0 失败返回-1,并且设置ERRNO

8、信息收发(TCP/UDP)

int recvsize = recv(sockfd , char * buffer , size_t rsize , int flags);
int ssize = send(sockfd,char * buffer , size_t wsize , int flags);
int readsize = read(int fd , void * buffer , size_t rsize );
//read函数会把参数fd所指的文件传送rsize个字节到buffer指针所指的内存中
//返回值为实际读取到的字节数, 返回0表示已到达文件尾或是无可读取的数据
int writesize = write(int fd , void * buffer , size_t wsize );
//write函数将buffer中的wsize字节内容写入文件描述符fd,成功时返回写的字节数
recvfrom(int socfd , char * buffer , size_t rsize ,int flags , struct sockaddr * addr , socklen_t * size );
//既可以读取数据,也可以传出发送方的网络信息结构体
sendto(int sockfd , void * buffer , size_t size , int flags , struct sockaddr * addr , size_t wsize);
//可以指定网络信息地址,向特定网络用户发送数据

三、基本TCP的C/S模型

在这里插入图片描述

四、实例:编写一个支持基本链接功能的客户端—服务端模型(TCP)
服务器

#include<sys/socket.h>
#include<arpa/inet.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#define SERVER_IP "192.168.27.128"
#define SERVER_PORT 8000int main(void)
{
    //1、定义初始化网络信息结构体struct sockaddr_in serveraddr; //服务器自己的struct sockaddr_in clientaddr; //传出的客户端的//2、根据需求设置网络信息serveraddr.sin_family = AF_INET;serveraddr.sin_port = htons(SERVER_PORT);inet_pton(AF_INET,SERVER_IP,&serveraddr.sin_addr.s_addr); //字符串ip转大端ip//3、创建sockfdint serverfd  =  socket(AF_INET,SOCK_STREAM,0);//创建tcp sockfd//4、socket与自定义网络信息绑定bind(serverfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr));//5、开启网络链接状态监听listen(serverfd,128);//6、服务端阻塞等待链接int clientfd;socklen_t addrsize = sizeof(clientaddr);printf("Server Process Pid %d Accepting... ...\n",getpid());char arrip[16];bzero(arrip,16);if((clientfd = accept(serverfd,(struct sockaddr*)&clientaddr,&addrsize))>0){
    //链接成功数据客户端网络信息printf("Server Process Pid %d Accept Success!\n",getpid());printf("client IP[%s] PORT[%d].\n",inet_ntop(AF_INET,&clientaddr.sin_addr.s_addr,arrip,16),ntohs(clientaddr.sin_port));}close(serverfd);close(clientfd);return 0;
}

客户端

#include<sys/socket.h>
#include<arpa/inet.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>#define SERVER_IP "192.168.27.128"
#define SERVER_PORT 8000int main(void)
{
    //1、定义初始化网络信息结构体struct sockaddr_in serveraddr; //服务器//2、设置服务器端网络信息serveraddr.sin_family = AF_INET;serveraddr.sin_port = htons(SERVER_PORT);inet_pton(AF_INET,SERVER_IP,&serveraddr.sin_addr.s_addr); //字符串ip转大端ip//3、创建sockfdint clientfd  =  socket(AF_INET,SOCK_STREAM,0);//创建tcp sockfd//4、请求与服务端链接if((connect(clientfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr))) == 0){
    printf("Client Process pid %d Connect Successly!\n",getpid());}close(clientfd);return 0;
}

在这里插入图片描述

  相关解决方案