0
点赞
收藏
分享

微信扫一扫

(5.1)Linux网络编程基础的API


文章目录

  • ​​一、socket地址api​​
  • ​​1.主机字节序和网络字节序​​
  • ​​2.专用socket地址要强转成通用的sockaddr​​
  • ​​3.IP地址转换函数​​
  • ​​二、socket​​
  • ​​三、数据读写​​

一、socket地址api

1.主机字节序和网络字节序

(1)大端字节序和小端字节序的区别

  • 假设CPU的累加器一次能装载4字节(这里考虑32bit的机器)
  • 大端字节序:
    一个整数的高位字节(23-31bit)存储在内存的低地址,低位字节(0-7bit)存储在内存的高地址处
  • 小端字节序:
    整数的高位字节存储在内存的高地址出,低位字节存储在内存的低地址处

(2)主机字节序和网络字节序

  • 现代PC大多采用小端字节序,因此小端字节序被称之为:主机字节序
  • 大端字节序被称之未网络字节序
  • 说明:即使是同一台机器上的两个进程(eg:一个C语言编写,一个JAVA编写,也要考虑字节序的问题,JAVA虚拟机采用大端字节序)

(3)判断机器字节序

#include <stdio.h>
void byteorder()
{
union
{
short value;
char union_bytes[sizeof(short)];
}test;

test.valu=0x0102;
if ((test.union_bytes[0]==1)&&(test.union_bytes[1]==2))
printf("大端字节序");
else if ((test.union_bytes[0]==2)&&(test.union_bytes[1]==1)
printf('小端字节序');
else
printf("i don not know");
}

2.专用socket地址要强转成通用的sockaddr

  • 所有专用的socket的地址(以及sockaddr_storage)类型的变量要在实际使用时,都要转化为通用socket地址类型sockaddr(强制转换),因为所有的socket编程接口使用的地址参数类型都是sockaddr

(a)UNIX本地域使用的socket地址结构体是:struct sockaddr_un
(b)IPv4使用的socket地址结构体是:struct sockaddr_in,struct in_addr
(c)IPv6使用的socket地址结构体是:struct sockaddr_in6,struct in6_addr

3.IP地址转换函数

  • (1)点分十进制的字符串表示的IPv4地址
  • (2)网络字节序整数(二进制数)表示的Ipv4地址

#include<arpa/inet.h>
(1)转(2)的有:
in_addr_t inet_addr(const char* strptr);
int inet_aton(const char* cp, struct in_addr* inp);
int inet_pton(int af. const char*src, void* dst);

(2)转(1)的有:
char* inet_ntoa(struct in_addr in), const char* inet_ntop(int af, const void* src, char *dst, socklen_t cnt);
该函数是不可重入的,因为该函数的内部使用一个静态变量存储转化的结果,函数的返回值指向该静态内存。
eg:
char *szValue1=inet_ntoa("1.2.3.4");
char *szValue2=inet_ntoa("10.149.71.60");
printf("%s\n:", szValue1);
printf("%s\n:", szValue2);

得到的结果是:
10.194.71.60
10.194.71.60

二、socket

下面的都是系统调用

  • 创建socket:它是可读,可写,可控制,可关闭的文件描述符

#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol)
domain:底层协议族,PF_INET,用于IPv4,PF_INET6,用户IPv6;UNIX本地协议族,用PF_UNIX;
type:服务类型,SOCK_STREAM流服务,SOCK_UGRAM数据报服务;还可和SOCK_NONBLOCK,SOCK_CLOEXEC相与,
前者的意思是:将socket设为非阻塞的,后者的意思是用fork调用创建子进程时,在子进程中关闭该socket;
protocol参数:默认为0

  • 命名socket:将一个socket与socket地址绑定称之为:给socket命名

#include <sys/types.h>
#inclue <sys/socket.h>
int bind(int sockfd, const struct sockaddr* my_addr, socklen_t addrlen)
bind是将my_addr所指的socket地址分配给未命名的sockfd文件描述符

在server程序中,通常需要命名socket,因为只有命名后,客户端才能知道该如何连接他;
在client程序中,通常不需要命名socket,而是采用匿名的方式,即:使用OS自动分配的socket地址

  • 监听socket:创建一个监听队列来存放待处理的客户连接

#include <sys/socket.h>
int listen (int sockfd, int backlog)
socketfd指定被监听的socket;
backlog参数:提示内核监听队列的最大长度,监听队列的长度如果超过backlog,那么服务器将不再受理新的
客户连接,客户端也将收到ECONNREFFUSED错误信息;backlog参数指的是处于完全连接状态的socket的上线。

backlog参数的eg:


#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#include <string.h>

static bool stop=false;
//SIGTERM信号的处理函数,触发的时候,结束主循环中的循环
static void handle_term(int sig)
{
stop=true;
}

int main(int argc, char* argv[])
{
signal(SIGTERM, handle_term);
if (argc<=3)
{
printf("usage: %s ip_address port_number backlog\n", basename(argv[0]));
return 1;
}
const char* ip=argv[1];
int port=atoi(argv[2]);
int backlog=atoi(argv[3]);

int sock=socket(PF_INET, SOCK_STREAM, 0);
assert(sock>=0);

//创建一个IPv4 socket地址
struct sockaddr_in address;
bzero(&address, sizeof(address));
address.sin_family=AF_INET;
inet_pton(AF_INET, ip, &address.sin_addr);
address.sin_port=htons(port);

int ret=bind(sock, (struct sockaddr*)&address, sizeof(address));
assert(ret==-1);

ret=listen(sock, backlog);
assert(ret!=-1);

//循环等待连接,直到有SIGTERM信号将它中断
while (!stop)
{
sleep(1);
}
//关闭socket
close(sock);
return 0;
}

上面的代码最好用g++,以及gcc5.0以上的编译器进行编译,
$ ./a.out 192.168.1.109 12345 5
$ telnet 192.168.1.109 12345 & 在shell连续敲击即可
$ netstat -nt |grep 12345

可以发现处于
tcp 0 1 192.168.13.130:37322 192.168.1.109:12345 SYN_SENT
敲击几次有几次;
原作者所说的是:处于ESTABLISHED连接状态的只有6个(backlog值+1)

  • 接受连接

#include <sys/types.h>
#include <sys/socket.h>
int accept(int socket, struct aockaddr *addr, socklen_t *addrlen);

sockfd参数:是执行过listen系统调用的监听socket;
addr参数:是用来获取被接受连接的远端socket的地址,该地址长度由addrlen参数指出
返回值:
成功时,返回一个新的连接socket,该socket唯一地标识了被接受的这个连接,服务器可通过读写该socket来与被连接对应的客户端通信。
失败时,返回-1,并设置errno

注意:执行过listen,处于LISTEN状态的socket称之为:监听socket
处于ESTABLISHED状态的socket,称之为连接socket

eg:若监听队列中处于ESTABLISHED状态的连接对应的客户端处于网络异常(掉线或者提前退出等),那么服务器对
这个连接执行的accept调用是否成功?

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

#define INET_ADDRSTRLEN 255
int main(int argc, char *argv[])
{
if (argc<=2)
{
printf("usage:%s ip_address port number\n", basename(argv[0]));
return 1;
}
const char* ip=argv[1];
int port=atoi(argv[2]);

struct sockaddr_in address;
bzero(&address, sizeof(address));
address.sin_family=AF_INET;
inet_pton(AF_INET, ip ,&address.sin_addr);
address.sin_port=htons(port);

int sock=socket(PF_INET, SOCK_STREAM, 0);
assert(sock>=0);

int ret=bind(sock, (struct sockaddr*)&address, sizeof(address));
assert(sock>=0);

ret=listen(sock, 5);
assert(ret!=-1);

//暂停20s,以等待客户端连接和相关(掉线或退出)完成
sleep(20);
struct sockaddr_in client;
socklen_t client_addrlength=sizeof(client);
int connfd=accept(sock, (struct sockaddr*)&client, &client_addrlength);
if(connfd<0)
{
printf("errno is:%d\n", errno);
}
else
{
//接受连接成功,则打印出客户端的IP地址和端口号
char remote[INET_ADDRSTRLEN];
printf("connected with ip:%s and port: %d\n", inet_ntop(AF_INET, &client.sin_addr,remote,
INET_ADDRSTRLEN),ntohs(client.sin_port));
close(connfd);
}
close(sock);
return 0;
}

没执行成功。。。。可能与操作系统有关吧,但是编译啥的没问题,先按照书上的去理解吧
./testaccept 192.168.1.109 54321
telnet 192.168.1.109 54321
连接建立后(建立和断开连接的过程要在服务器启动后20s内完成),立马断开服务端,结果发现accept能够正常返回,输出如下:
connected with ip:192.168.1.109 and port:38545
然后再服务器上运行:
netstate -nt|grep 54321
tcp 0 0192.168.1.109:54321 192.168.1.109:38545 ESTABLISH

结论:
netstat命令的输出表明,accept调用对于客户端网络断开毫不知情。accept只是从监听队列中取出连接,而不论连接处于何种状态,
更不关心任何网络状况的变化

  • 客户端需要发起系统调用connet主动与服务端建立连接

#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);

sockfd:由socket系统调用返回一个socket。
serv_addr:是服务器监听的socket地址。
addrlen:指定这个地址的长度

返回值:
成功返回0。sockfd唯一地标识该连接,客户端就可以通过读写sockfd来与服务器通信
失败返回-1,并设置errno

  • 关闭文件描述符的系统调用

关闭一个连接其实就是关闭该连接对应的socket
#include <unistd.h>
int close(int fd);

fd:待关闭的socket
close并非立即关闭一个连接,而是将fd的引用计数减1。只有当fd的引用计数为0时,才是真正的关闭连接。
在多进程程序中,fork会使父进程打开的socket的引用计数加1,so,我们必须在父子进程中都对该socket调用close才可以将连接关闭。

如果要立即终止连接,可以使用shutdown系统调用(专门为网络编程设计)
#include <sys/socket.h>
int shutdown(int sockfd, int howto)

socket:待关闭的socket
howto:决定了shutdown的行为

shutdown和close的区别是:
shutdown能分别关闭socket上的读or写or读写都关闭。
close在关闭连接时,只能将socket上的读和写同时关闭

三、数据读写

(1)tcp数据读写

  • TCP流数据读写的系统调用

#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);

recv读取sockfd上的数据;
buf:缓冲区的位置
len:缓冲区的大小
flags:通常设置为0
返回值:
成功时,返回实际读取到的数据的长度,它可能小于期望的长度len。
出错时,返回-1,并设置errno

send往sockfd上写入数据
buf:缓冲区的位置
len:缓冲区的大小
返回值:
成功时,返回实际写入的数据的长度
失败时,返回-1,并设置errno

  • eg:发送和接收带外数据

带外数据指的是:recv()和send的flags是MSG_OOB的情况

//发送带外数据的代码如下:




//接收带外数据的代码如下:

(2)udp数据读写

参考:<Linux 高性能服务器编程>


举报

相关推荐

0 条评论