目录
1.点分十进制转网络字节序(string ---> uint_32)
2.网络字节序转点分十进制(uint_32 ---> string)
(一). SOCKET编程
1).socket
一个fd指向一个socket,内核开辟两个缓冲区与之对应,而管道是一个fd对应一个缓冲区
2).网络字节序转换函数
网络字节序是大端方式,字地址存放高字节
主机转网络字节序:htonl,htons
网络字节序转主机:ntohl,ntohs
3).IP地址转换函数
1.点分十进制转网络字节序(string ---> uint_32)
int inet_pton(int af, const char * src, void * dst);
af: AF_INET 、AF_INET6
src: 传入的是点分十进制ip地址
dst: 转化后的网络字节序的ip地址,是无符号32位整型数
返回值: 成功1
异常0(src不是一个有效的ip地址)
失败-1
使用举例:
int dst;
inet_pton(AF_INET, "192.168.0.1", &dst);
经过inet_pton函数的转换,dst已经存放了变成网络字节序的ip地址
2.网络字节序转点分十进制(uint_32 ---> string)
const char * inet_ntop(int af, const void * src, char * dst, socklen_t size)
af: AF_INET 、AF_INET6
src: 网络字节序的ip地址
dst: 转化后的点分十进制ip地址
size: dst的大小(一般取16)
使用前,定义一个char ip[16]数组来存储点分十进制ip地址字符串
4).sockaddr地址结构
man 7 ip 可以查看详细内容
struct sockaddr_in //IPv4协议地址结构体,这个才是我们要用到的结构体
{
sa_family_t sin_family; //地址族,固定值AF_INET, IPv4
in_port_t sin_port; //端口号,通常使用htons()计算获得
struct in_addr sin_addr; //IP地址,具体结构体见下
}
struct in_addr{
uint32_t s_addr; //网络字节序的ip地址
}
使用举例:
①定义ipv4地址结构体变量
struct sockaddr_in addr;
②初始化结构体
addr.sin_family = AF_INET;
addr.sin_port = htons(9537);
(对于addr.sin_addr.s_addr,有两种初始化的方法,一般取第二种)
//第一种
int dst;
inet_pton( AF_INET, "192.168.0.1", (void*)&dst );
addr.sin_addr.s_addr = dst;
//第二种
addr.sin_addr.s_addr = htonl(INADDR_ANY); //取出系统中任意有效的ip地址
③使用时将其转化为struct sockaddr类型
5).网络套接字函数
1.socket函数
作用:
创建一个套接字
所需头文件:
#include<sys/types.h>
#incluede<sys/socket.h>
函数原型:
int socket(int domain, int type, int protocol);
函数参数:
domain: AF_INET、AF_INET6、AF_UNIX(本地通信)
type:
SOCK_STREAM(流式套接字,针对TCP协议)
SOCK_DGRAM(数据报套接字/报式套接字,针对UDP协议)
SOCK_RAW(原始套接字,针对IP/ICMP协议)
protocol:
协议值,通常情况下为0,即默认IP协议
函数返回值:
成功:新套接字对应的文件描述符
失败:-1,并设置errno,可以用perror报错
注意:套接字在Linux内核中是一个结构十分复杂的结构体,使用socket()创建一个套接字会在内核里生成一个套接字结构体并返回该套接字结构体的文件描述符。
使用举例: int fd = socket(AF_INET, SOCK_STREAM, 0);
2.bind函数
作用:
给socket绑定一个地址结构(IP+port)
所需头文件:
#include<sys/types.h>
#include<sys/socket.h>
函数原型:
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
函数参数:
sockfd:
socket函数的返回值,即:需要绑定的套接字的文件描述符
addr:
该参数是一个结构体指针,必须使用地址传递的方式传参。需要针对不同的协议设定不 同的结构体类型
eg:
struct sockaddr_in addr;
addr.sin_familt = AF_INET;
addr.sin_port = htons(8888);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
那么最后的传参是: (struct sockaddr*)&addr
addrlen:
地址结构的大小,通常为sizeof(struct sockaddr_in),或者sizeof(addr)
函数返回值:
成功:0
失败:-1,并设置errno
注意:绑定套接字函数bind()将套接字与需要进行网络通信的地址信息建立连接。服务器端必须进行bind()操作,但客户端可以不手动进行bind(),等到通信连接后客户端可以自动进行bind()操作。
3.listen函数
作用:
设置同时与服务器建立连接的上限数(同时进行三次握手的客户端数量)
所需头文件:
#include<sys/types.h>
#include<sys/socket.h>
函数原型:
int listen(int sockfd, int backlog)
函数参数:
sockfd:
socket函数的返回值,即:设置监听的套接字的文件描述符,必须是流式套接字
backlog:
请求队列的最大值,最大值为128
函数返回值:
成功:0
失败:-1,并设置errno
注意:listen不是用来阻塞监听的,而是用来设置同时建立连接的客户端的上限,accept才是
4.accept函数
作用:
阻塞等待客户端建立连接,成功则返回一个与客户端成功建立连接的socket对应的文件描述符
所需头文件:
#include<sys/types.h>
#include<sys/socket.h>
函数原型:
int accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen)
函数参数:
sockfd:
socket函数的返回值,
addr:
( 是传出参数,这点很重要,而bind()函数里的addr有const修饰,是传入参数 )
//int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
//int accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen)
成功与服务器建立链接的那个客户端的地址结构
addrlen:
传入的是addr的大小的地址
传出的是客户端addr的实际大小的地址
传入传出的大小可能会变
eg:
socklen_t clit_addr_len = sizeof(addr);
那么传进去的参数为&clit_addr_len
函数返回值:
成功:能与服务器进行数据通信的socket对应的fd
失败:-1,并设置errno
5.connect函数
作用:
使用现有的socket与服务器建立连接,是客户端用的函数
客户端先用socket()函数创建一个套接字,紧接着就调用connect()函数
所需头文件:
#include<sys/types.h>
#include<sys/socket.h>
函数原型:
int connect(int sockfd, const struct sockaddr* addr, socklen_t* addrlen)
函数参数:
sockfd:
socket函数的返回值,
addr:
是传入参数,而且是服务器的地址结构
因为我要和服务器创建的套接字建立连接,服务器的套接字此时已经绑定了地址结构
eg:
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERVPORT);
inet_pton(AF_INET, "服务器ip地址",&serv_addr.sin_addr.s_addr);
addrlen:
服务器的地址结构的大小传入的是addr的大小的地址
函数返回值:
成功:0
失败:-1,并设置errno
注意:如果不使用bind绑定客户端地址结构,系统会采用隐式绑定的方式自动帮我们绑定,也就是说在客户端方面,我们不需要自己手动绑定
6).TCP通信流程分析
server:
1.socket() 创建套接字
2.bind() 绑定服务器地址结构
3.listen() 设置监听上限
4.accept() 阻塞监听客户端连接
5.read() 读socket获取客户端数据
6.write()
7.close()
client:
1.socket() 创建套接字
2.connect() 与服务器建立连接
3.write() 写数据到socket
4.read() 读服务器的数据
5.close()
7).实现server
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
int main(int argc, char* argv[])
{
int lfd = 0;//用于监听
int cfd = 0;//用于通信
char buf[BUFSIZ];//BUFSIZ是系统自带的宏4096
int ret;
struct sockaddr_in serv_addr,clit_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(9527);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
//inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr.s_addr);
char ip[16] = "";//保存点分十进制ip地址
socklen_t clit_addr_len;
//1.创建套接字lfd用于监听
lfd = socket(AF_INET, SOCK_STREAM, 0);
if(lfd == -1){
perror("socket error");
exit(1);
}
//2.绑定ip和port
bind(lfd, (struct sockaddr*)&servaddr, sizeof(serv_addr));
//3.设置监听上限
listen(lfd, 128);
//4.阻塞监听等待客户端建立连接
cfd = accept(lfd, (struct sockaddr)&clit_addr, &clit_addr_len);
if(cfd == -1){
perror("accept error");
exit(1);
}
printf( "client IP: %S port: %d\n",
inet_ntop(AF_INET, &clit_addr.sin_addr.s_addr, ip, 16)
ntohs(clit_addr_sin_port) );
//5.通信
while(1){
memset(buf, sizeof(buf), 0);
ret = read(cfd, buf, sizeof(buf));//从cfd里面读数据,读到buf里面保存
write(STDOUT_FILENO, buf, ret);//把buf里面的数据写到终端(打印)
for(int i = 0; i < ret, i++){
buf[i] = toupper(buf[i]);
}
write(cfd, buf, ret);//把修改后的buf里面的数据写到cfd里面去
}
//6.关闭服务端
close(lfd);
close(cfd);
return 0;
}
运行后,执行命令 nc 127.0.0.1 9527
终端输入 hello 回车(相当于客户端的数据通过客户端的套接字发到cfd里面的读端)
终端会显示小写的hello与大写的hello
8).实现client
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#define SERV_PORT 9527
int main(int argc, char* argv[])
{
int cfd;
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERV_PORT);
inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr.s_addr);
char buf[1024];
int ret;
cfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TIP);
if(cfd == -1){
perror("socket");
exit(1);
}
if(0 == connect(cfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) ){
perror("connect");
exit(1);
}
int cnt = 10;
while(cnt){
write(cfd, "hello\n", 6);//写到客户端的套接字对应的cfd指向的写缓冲区,然后发给服务器
ret = read(cfd, buf, sizeof(buf))//服务器将数据发到cfd对应的读缓冲区,
//读取数据到buf里面去
write(STDOUT_FILENO, buf, ret)//将buf里面的数据打印输出
sleep(1);
cnt--;
}
close(cfd);
return 0;
}
9).端口复用技术
作用:防止服务器关闭再重启后的2msl时间内端口无法重复利用
在bind之前使用setsockopt函数:
int on = 1;
if( setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0 ){
perror("setsockopt");
exit(1);
}
10).多进程服务器
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
void do_service(int cfd)
{
char recvbuf[1024];
while(1)
{
memset(recvbuf, 0, sizeof(recvbuf));
int ret = read(cfd, recvbuf, sizeof(recvbuf));//从cfd里面读数据到recvbuf里面
if(ret == 0)
{//说明没数据了,客户端关闭了,可以退出函数体返回
printf("client close\n");
break;
}
else if(ret == -1)
{
perror("read");
exit(1);
}
else
{
fputs(recvbuf, stdout);
write(cfd, recvbuf, ret);
}
}
}
int main(int argc, char* argv[])
{
int lfd = 0;//用于监听
int cfd = 0;//用于通信
//1.创建套接字lfd用于监听
lfd = socket(AF_INET, SOCK_STREAM, 0);//第三个参数0可以用宏IPPROTO_TCP替代
if(lfd == -1){
perror("socket");
exit(1);
}
//2.绑定ip和port
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(5188);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
//inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr.s_addr);
int on = 1;
if( setsockopt(lfd, SOL_SOCKET, SOREUSEADDR, &on, sizeof(on)) < 0 ){
perror("setsockopt");
exit(1);
}
if( bind(lfd, (struct sockaddr*)&servaddr, sizeof(serv_addr)) < 0 ){
perror("bind");
exit(1);
}
//3.设置监听上限
if( listen(lfd, 128) < 0 ){
perror("listen");
exit(1);
}
//4.阻塞监听等待客户端建立连接与通信
struct sockaddr_in clit_addr;
socklen_t clit_addr_len = sizeof(clit_addr);
pid_t pid;
char ip[16] = "";//保存点分十进制ip地址
while(1)
{
cfd = accept(lfd, (struct sockaddr)&clit_addr, &clit_addr_len);
if(cfd == -1){
perror("accept");
exit(1);
}
printf( "client IP: %S port: %d\n",
inet_ntop(AF_INET, &clit_addr.sin_addr.s_addr, ip, 16)
ntohs(clit_addr.sin_port) );
pid = fork();
if(pid == -1){perror("fork");exit(1)}
if(pid == 0)
{//子进程不需要监听套接字lfd
close(lfd);
do_service(cfd);
exit(1);
}
else
{//父进程不需要已连接套接字cfd
close(cfd);
}
return 0;
}
运行情况:
用一个客户端连接服务器,在客户端输入hello回车,那么数据会从客户端发送给服务器,服务器端会显示hello,然后将hello写到cfd,数据就会又发给客户端,客户端会显示hello,客户端在终端使用ctrl+c命令会关闭终端,那么服务器端读不到数据会显示client close