Linux初学第十六天<网络编程二、socket API介绍及服务器配置>

一、socket API介绍

        昨天已经学习了网络编程的概念和编程步骤,今天学习相关的API之后,就可以参照昨天的编程步骤来敲代码。
        首先先对昨天的学习留下的一个知识点进行补充,我们在学到字节序的时候,有几个API需要介绍一下,也会在编程中使用到:

#include <netinet/in.h>

uint16_t htons(uint16_t host16bitvalue);//返回16位网络字节序
uint32_t htonl(uint32_t host32bitvalue);//返回32位网络字节序
uint16_t ntohs(uint16_t net16bitvalue);//返回主机字节序的值,uint32_t
ntoh(uint32_t net32bitvalue);//返回主机字节序的值

这几个函数通常把端口号转换成网络字节序配置给socket

1.指定连接协议 socket()

        socket 相关API大部分都放在下面这两个头文件中:
                 #include <sys/types.h>
                 #include <sys/socket.h>
函数原型: int socket(int domain,int type,int protocol)
参数说明::
                  1. domain 为协议族类型,比如 IPV4,IPV6等;
d o m a i n { A F _ I N E T : I P V 4 因 特 网 协 议 A F _ I N E T 6 : I P V 6 因 特 网 协 议 A F _ U N I X : U n i x A F _ R O U T E : 路 由 套 接 字 A F _ K E Y : 密 钥 套 接 字 A F _ U N S P E C : 指 定 其 他 协 议 domain\left\{ \begin{array}{c} AF\_INET :IPV4因特网协议\\ AF\_INET6:IPV6 因特网协议\\ AF\_UNIX:Unix \\ AF\_ROUTE:路由套接字\\ AF\_KEY:密钥套接字\\ AF\_UNSPEC:指定其他协议\end{array}\right. domainAF_INET:IPV4AF_INET6:IPV6AF_UNIX:UnixAF_ROUTEAF_KEYAF_UNSPEC:
                  2. type 指定协议类型,TCP或UDP啥的:
t y p e { S O C K _ S I R E A M : 流 式 套 接 字 , T C P 协 议 S O C K _ D G R A M : 数 据 包 套 接 字 , U D P 协 议 S O C K _ R A M , : 允 许 使 用 低 层 协 议 , I P 或 T C M P type\left\{ \begin{array}{c} SOCK\_SIREAM:流式套接字,TCP协议\\ SOCK\_DGRAM:数据包套接字,UDP协议\\ SOCK\_RAM,:允许使用低层协议,IP或TCMP\end{array}\right. typeSOCK_SIREAM:,TCPSOCK_DGRAMUDPSOCK_RAM,:使IPTCMP
                  3. protocol 通常赋值0,选择type类型对应的默认协议。
返回值: 成功返回网络描述符,同文件描述符的作用相同,起到索引作用;失败返回 -1
用法:

int socket_fd;//定义一个网络描述符
socket_fd=socket(AF_INET,SOCK_SIREAM,0);

2.IP地址和端口号配置bind()

函数原型: int bind (int sockfd,const struct sckaddr *addr,socklen_t addrlen)
参数说明:
                  1. sockfd 网络描述符,代表需要配置的网络;
                  2. *addr一个包含有IP地址和端口号的结构体指针;
                  3. addrlen 为 *addr的大小,可用sizeof来计算;
返回值: 成功返回0;失败返回 -1
对 *addr 的补充说明: addr是个结构体指针:当协议族为 IPv4 时,它长这样:

struct sockaddr{
	as_family_t as_family;//这是协议,AF_INET
	char sa_data[14];//存的是IP地址和端口后
}

而我们通常用 sockaddr_in 来配置addr,但是因为bind()函数需要sckaddr* 类型的结构体指针,赋值sockaddr_in时要做强制转换,sockaddr_in 结构体长这样,在 “/usr/include” 目录下,用 grep "struct sockaddr_in { " * -nir 指令来查找:

struct sockaddr_in {
	__kernel_sa_family_t sin_family;//这是协议,AF_INET
	__be16                  sin_port;//这是端口号
	struct in_addr        sin_addr;//这是IP地址,它也是一个结构体
}

in_addr 结构体长这样:

struct in_addr {
        __be32  s_addr;
};

注意sin_port 在赋值的时候,需要用到文章开头时提到的网络字节序转换函数,比如:我们配置端口为8888(端口最好是5000以上)时,sin_port=htons(8888);这样才能有效地把端口号配置给socket。而IP地址在编程中IP地址是字符串形式,也需要把转换成网络能够识别的形式,有这么一个函数:
        int inet_aton(const char *straddr,struct in_addr *addr);
这个函数的作用就是把straddr IP地址转换为网络地址存到 *addr 中,成功返回0,失败返回 -1;所以我们在配置IP地址时,不能sockaddr_in.sin_addr=inet_aton(“192.xxx.xxx.xxx”,&addr);
应该是:inet_aton(“192.xx.xxx.xx”,&sockaddr_in.sin_addr);
        有把字符串的IP地址转换为网络能识别的地址的函数,也有把网络地址转换为字符串IP地址的函数:
        char * inet_ntoa(struct in_addr addr);
返回一个字符串形式的IP地址,addr就是需要被转换的地址。
用法:

int bind_ret,addrlen;
struct sockaddr_in sockaddr;

sockaddr.sin_family=AF_INET;//配置协议
sockaddr.sin_port=htons(8888);//配置端口号
inet_aton("192.xx.xxx.xx",&sockaddr.sin_addr);//配置IP地址
addrlen=sizeof(struct sockaddr_in *);

bind_ret=bind(socket_fd,(struct sockaddr *)&sockaddr,addrlen);

3.监听连接 listen()

函数原型: int listen(int sockfd, int backlog);
参数说明:
                  1. sockfd 为网络描述符;
                  2. backlog允许连接的最大数量。
返回值: 成功返回0,失败返回 -1
        listen函数只用于服务器,因为服务器并不知道该主动去连接哪个客户端,只能一直监听是否有其他客户端请求连接,然后相应该连接。
用法:

listen(sockfd,10);//最大只能连接10个客户端

4.响应连接 accept()

函数原型: int accept(int sockfd, struct sckaddr *addr,socklen_t *addrlen);
参数说明:
                  1. sockfd 为网络描述符;
                  2. *addr 已连接的客户端IP地址;
                  3. *addrlen 客户端IP地址的大小;
返回值: 成功返回一个客户端描述符,用于后续给客户端发送数据或读取数据,失败返回 -1
        这个客户端描述符相当于一个文件描述符,可以让 writeread来使用,我们发送数据和读取数据就是用 writeread。服务器配置到这里,客户端就可以连接了。
用法:

int acc_fd;
char readbuf[128];
struct sockaddr_in a_addr;
int addrlen=sizeof(struct sockaddr_in);
acc_fd=accept(socket_fd,(struct sockaddr *)&a_addr,&addrlen);
read(acc_fd,&readbuf,sizeof(readbuf));
write(acc_fd,"I am Server!",strlen("I am Server!"));

二、服务器配置实战

        利用上面的API,配置一个服务器,让其可以被它电脑使用telnet 来连接,并打印出已连接的客户端的IP地址。

1.demo1.c详细代码

#include <sys/types.h>         
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main()
{
        int socket_fd;
        int bind_ret;
        int addrlen;
        int accept_num;
        struct sockaddr_in s_addr;
        struct sockaddr_in a_addr;
        //1.socket
        socket_fd=socket(AF_INET,SOCK_STREAM,0);//套接字指定协议为TCP/IPv4
        //2.bind()
        s_addr.sin_family=AF_INET;
        s_addr.sin_port=htons(8888);
        inet_aton("192.168.16.4",&s_addr.sin_addr);
        bind_ret=bind(socket_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));//配置IP地址和端口

        //3.listen()
        listen(socket_fd,10);//监听
        //4.accept()
        addrlen=sizeof(struct sockaddr_in);
        printf("wait Client...\n");
        accept_num=accept(socket_fd,(struct sockaddr *)&a_addr,&addrlen);//连接
        if(accept_num==-1){
                printf("No client connect!");
        }
        printf("Connect client ip=%s\n",inet_ntoa(a_addr.sin_addr));
        //write/read

        return 0;
}

因为Linux操作系统是以虚拟机的形式存在,和我们Windows本质上是两台不同电脑,有各自的IP地址,Linux可以使用ifconfig指令查看IP地址,Windows运行cmd 输入ipconfig 查看IPv4的IP地址。

2.运行结果演示:

        先运行我们服务器程序,Window打开运行,输入cmd打开终端,然后输入 telnet 这里输入IP地址 输入端口号,回车执行,成功连接就是这个样子:
在这里插入图片描述
注意: 第一次运行执行 telnet可能会出现以下提示:
在这里插入图片描述
这是因为Windows 的Telnet Client 没有打开:点击开始菜单里的设置按钮,
在这里插入图片描述

这样就能Telnet 成功了,扯远了:继续实验,既然能够连接了,那么我们试一下发送和接受吧,复制demo1.c 为demo2.c,在demo2.c添加writeread

3.demo2.c详细代码

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
int main()
{
        int socket_fd;
        int bind_ret;
        int addrlen;
        int accept_num;
        int s_read;
        char readBUf[128];

        struct sockaddr_in s_addr;
        struct sockaddr_in a_addr;
        memset(&s_addr,0,sizeof( struct sockaddr_in));
        memset(&a_addr,0,sizeof( struct sockaddr_in));
        //1.socket
        socket_fd=socket(AF_INET,SOCK_STREAM,0);

        //2.bind()
        s_addr.sin_family=AF_INET;
        s_addr.sin_port=htons(8888);
        inet_aton("192.168.16.4",&s_addr.sin_addr);

        bind_ret=bind(socket_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));

        //3.listen()
        listen(socket_fd,10);
        //4.accept()
        addrlen=sizeof(struct sockaddr_in);
        printf("wait Client...\n");
        accept_num=accept(socket_fd,(struct sockaddr *)&a_addr,&addrlen);
        if(accept_num==-1){
                perror("accept");

        }

        printf("Connect client ip=%s\n",inet_ntoa(a_addr.sin_addr));
        //write/read
        s_read=read(accept_num,readBUf,128);//读取数据
        if(s_read==-1){
                perror("read");
        }else{

                printf("Read buf=%s\n",readBUf);
        }

        write(accept_num,"I am Server!",strlen("I am Server!"));//发送数据后结束

        return 0;
}

4.运行结果演示:

        在连接成功后,摁任意一个键(我摁了“s”),
在这里插入图片描述
完成了服务器的配置,客户端的配置就没有那么复杂了,但是这个要等过两天才会出关于关于客户端配置的博客,因为今天是星期五。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值