Linux下Socket简单通信

socket是计算机之间通信的一种约定和方式,通过这种方式和约定,计算机可以与相同约定的计算机进行数据传输。

c/s模式 服务端socket
1.创建套接字:

int socket(int af,int type,int protocol);

1)参数af:
IP地址类型 AF_INET (ipv4),AF_INET6 (ipv6);
2)参数type:
数据传输方式 SOCK_STREAM SOCK_DGRAM
① SOCK_STREAM
表示面向连接的数据传输方式。数据可以准确无误地到达另一台计算机,如果损坏或丢失,可以重新发送,但效率相对较慢。常见的 http 协议就使用 SOCK_STREAM 传输数据,因为要确保数据的正确性,否则网页不能正常解析。
② SOCK_DGRAM
表示无连接的数据传输方式。计算机只管传输数据,不作数据校验,如果数据在传输中损坏,或者没有到达另一台计算机,是没有办法补救的。也就是说,数据错了就错了,无法重传。因为 SOCK_DGRAM 所做的校验工作少,所以效率比 SOCK_STREAM 高。
3)参数protocol:协议,常用PROTOCOl_TCP,PROTOCOL_UDP
就是网络通信的约定,通信的双方必须都遵守才能正常收发数据。协议有很多种,例如 TCP、UDP、IP 等,通信的双方必须使用同一协议才能通信。协议是一种规范,由计算机组织制定,规定了很多细节,例如,如何建立连接,如何相互识别等。

2.创建sockaddr_in结构体变量

struct sockaddr_in serv_addr;    //实例化sockaddr_in结构体变量
memset(&serv_addr,0,sizeof(serv_addr));    //将结构体用0填充
serv_addr.sin_family = AF_INET;    //使用IPv4协议
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");   //具体IP地址
serv_addr.sin_port = htons(1234);   //端口

3.将套接字与IP和端口绑定

bind(serv_sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr));

参数列表:1.实例化的套接字 2.sockaddr_in的地址 3.结构体的大小

第二个参数 存在一个强转的过程,将sockaddr_in强转成sockaddr结构体。
首先可以来看一下sockaddr_in结构体(IPv4)的物理模型

struct sockaddr_in
{
    sa_family_t    sin_family;   //地址族,地址类型 2字节
    uint16_t       sin_port;        //端口号 2字节
    struct in_addr sin_addr;  //in_addr结构体 4字节
    char           sin_zero[8];         //全是0填充,不使用
};

在sockaddr_in结构体中也存在in_addr结构体

struct in_addr
{
    in_addr_t s_addr;   //32位IP地址
};

in_addr_t 在头文件 < netinet/in.h> 中定义,等价于 unsigned long,长度为4个字节。也就是说,s_addr 是一个整数,而IP地址是一个字符串,所以需要inet_addr()函数转换。
(至于为什么要进行结构体内嵌套结构体而不直接使用单个变量,据说是历史遗留问题)
为什么不直接使用sockaddr结构体?我们再来看一下sockaddr结构体的物理模型

struct sockaddr
{
    sa_family_t  sin_family;    //地址族 2字节
    char         sa_data[14];   //14字节的字符数组 IP地址和端口及8字节的字符数组
};

可以从上看出,在其中是将IP地址和端口号包括8字节的字符数组存放在一个14字节的字符数组中。如果直接用sockaddr结构体,需要将IP地址和端口号直接赋值sa_data数组。

如”127.0.0.1:1234”字符串转成字符型数组,不仅没有实现的函数,而且也不方便。sockaddr_in和sockaddr都是16字节的结构体,在强转过程 并不会有数据丢失。

可以说sockaddr是通用的ip地址和端口存储结构体,为不同的IP协议提供相同的存储结构。

在将socket进行绑定之后,还需将socket进入被动监听状态,再通过accept()函数就能接收到客户端的请求了。

listen()函数:int listen(int sock, int backlog);

sock 为需要进入监听状态的套接字,backlog 为请求队列的最大长度。当socket进入被动监听状态后,当没有客户端请求时,socket处于”睡眠”状态,当有请求时,socket被”唤醒”来响应请求。

当socket在响应请求时,其他客户端的请求就会被搁置,进入缓冲区进行排队,直到缓冲区被填满,这个缓冲区就是请求队列,在socket处理完请求之后,会将队列中的请求顺序响应。

缓冲区的长度就是有backlog参数指定。当缓冲区队满时,就不再接收新的请求,对于 Linux,客户端会收到 ECONNREFUSED 错误。

在socket进入被动监听状态时,还需要accept()来接收请求。

accept()原型:int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);
参数列表:①sock 服务端套接字,addr 客户端IP地址和端口,addrlen为*addr长度。

accept() 返回一个新的套接字来和客户端通信,之后的数据传输都是对这个新的套接字文件进行读写。
程序执行到accept()会进入阻塞状态直到,服务端收到请求,listen()只是让服务端socket进入监听状态,但是listen()之后的代码还是会执行直到遇到accept()函数。

在两台计算机通过socket连接起来之后,就能通过write()函数和read()函数发送和接收数据了。也可使用revc()函数和send()函数

write()函数原型:ssize_t write(int fd, const void *buf, size_t nbytes);

fd 为要写入的文件的描述符,即accept()产生的新套接字文件,buf 为要写入的数据的缓冲区地址,nbytes 为要写入的数据的字节数。
write()会将buf缓冲区中的nbytes字节大小的数据写入fd文件中。写入成功返回写入字节数,失败则返回-1。

read()函数原型:ssize_t read(int fd, const void *buf, size_t nbytes);

fd 为要读取的文件的描述符,buf 为要接收的数据的缓冲区地址,nbytes 为要写入的数据的字节数。
read()会将fd文件中读取nbytes字节保存在buf缓冲区,成功则返回读取到的字节数(但遇到文件结尾则返回0),失败则返回 -1。

而revc()函数,send()函数和read() ,write()函数基本相同,但有4个参数,前三个参数相同 第4个参数置0或NULL ,初学者不必深究。

最后将创建的套接字文件关闭。

服务端socket实例:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main(){
    //创建套接字
    int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    //将套接字和IP、端口绑定
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));  //每个字节都用0填充
    serv_addr.sin_family = AF_INET;  //使用IPv4地址
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  //具体的IP地址
    serv_addr.sin_port = htons(1234);  //端口
    bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
    //进入监听状态,等待用户发起请求
    listen(serv_sock, 20);
    //接收客户端请求
    struct sockaddr_in clnt_addr;
    socklen_t clnt_addr_size = sizeof(clnt_addr);
    int clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
    //向客户端发送数据
    char str[] = "Hello World!";
    write(clnt_sock, str, sizeof(str));

    //关闭套接字
    close(clnt_sock);
    close(serv_sock);
    return 0;
}

客户端socket实例:
客户端不需要listen()函数监听,相对服务端accept()函数,客户端使用connect()连接。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
int main(){
    //创建套接字
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    //向服务器(特定的IP和端口)发起请求
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));  //每个字节都用0填充
    serv_addr.sin_family = AF_INET;  //使用IPv4地址
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  //具体的IP地址
    serv_addr.sin_port = htons(1234);  //端口
    connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

    //读取服务器传回的数据
    char buffer[40];
    read(sock, buffer, sizeof(buffer)-1);

    printf("Message form server: %s\n", buffer);

    //关闭套接字
    close(sock);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值