一、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.
domain⎩⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎧AF_INET:IPV4因特网协议AF_INET6:IPV6因特网协议AF_UNIX:UnixAF_ROUTE:路由套接字AF_KEY:密钥套接字AF_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.
type⎩⎨⎧SOCK_SIREAM:流式套接字,TCP协议SOCK_DGRAM:数据包套接字,UDP协议SOCK_RAM,:允许使用低层协议,IP或TCMP
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;
这个客户端描述符相当于一个文件描述符,可以让 write和 read来使用,我们发送数据和读取数据就是用 write和 read。服务器配置到这里,客户端就可以连接了。
用法:
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添加write 和 read
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”),
完成了服务器的配置,客户端的配置就没有那么复杂了,但是这个要等过两天才会出关于关于客户端配置的博客,因为今天是星期五。