一、linux socket 基础
任务:
- 客户端发送小写字母,服务器返回大写字母。
- 客户端发送正确的运算(1 + 3),服务器返回结果
- 客户端发送一个数字数组,服务器返回其中最大和最小的值
网络基础
ip地址: 在网络环境中唯一标识一台主机(NAT等除外)
端口号:计算上每个占用一个端口,网络通讯时候,要选择通讯程序的端口号。
socket:在编程程序时候用到,为udp和tcp的上层接口。socket在linux中为一种文件类型(伪文件)。有两个缓冲区,一个读数据,一个写数据,为全双工工作模式。如图:
注意事项:
- socket一定是成对出现的
- socket一顶要绑定ip 和端口
- 一个文件描述符指向两个缓冲区(一个读缓冲,一个写缓冲)
tcp/ip传输
tcp/ip的传输为大端传输,但是不用的主机大小端不同。在编写网络程序过程中,需要网络字节序和主机字节序的转换。
其实函数有:
//该类函数的头文件
#include <arpa/inet.h>
// 主机数转换成无符号长整型的网络字节顺序
uint32_t htonl(uint32_t hostlong);
//是将整型变量从主机字节顺序转变成网络字节顺序
uint16_t htons(uint16_t hostshort);
// 是将一个无符号长整形数从网络字节顺序转换为主机字节顺序,
// ntohl()返回一个以主机字节顺序表达的数。
uint32_t ntohl(uint32_t netlong);
//作用是将一个16位数由网络字节顺序转换为主机字节顺序。
uint16_t ntohs(uint16_t netshort);
注意事项:
- 大端:地址值-高位 (高地址-地位)
- 小段:低-低 高-高
例:
大小端的转换的函数:
#include <arpa/inet.h>
//将“点分十进制”转换为“网络直接序”
int inet_pton(int af, const char *src, void *dst);
//将“网络字节序”转换为“点分十进制”
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
在网络编程中,用sockaddr结构体来保存协议和ip。后面演变为sockaddr_in,只是在原基础上添加了一些东西。
struct sockaddr {
//保存使用的协议,一般是AF_xxx格式
sa_family_t sa_family; /* address family, AF_xxx */
//保存目标地址ip地址
char sa_data[14]; /* 14 bytes of protocol address */
};
struct sockaddr_in {
__kernel_sa_family_t sin_family; /* Address family */ 地址结构类型
__be16 sin_port; /* Port number */ 端口号
struct in_addr sin_addr; /* Internet address */ IP地址
...
}
/***struct sockaddr_in中的struct in_addr结构体***/
struct in_addr { /* Internet address. */
__be32 s_addr;
};
客户端需要给服务端发送数据,同时也有接受数据的需求。对于服务端,亦然。对着数据接受和发送,有相应的函数:
/***************数据的接受************/
ssize_t read(int fd, void *buf, size_t count);
sszie_t recv(int sockfd,void *buf, size_t len, int flags);
/************************************/
/***************数据的发送************/
ssize_t write(int fd, const void *buf, size_t count);
ssize_t send(int sockfd, const void *buf, size_t len, flags)
/************************************/
注:
- 上面的flags一般复制为0 .
socket模型创建流程图:
服务端代码:
/******创建套接字******/
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
/*****绑定本地ip和端口*******/
struct sockaddr_in serv;
//初始化serv
memset(serv,0, sizeof(serv));
//确定为ipv4
serv.sin_family = AF_INET;
//端口,把本地字节序转换为网络字节序
serv.port = htons(port);
/*将serv的ip设置为 INADDR_ANY,接受任意的ip的客户端发来的数据。htonl将本地字节序转换为网络字节序
*/
serv.sin_addr.s_addr = htonl(INADDR_ANY);
//绑定
bind(listenfd,(struct sockaddr*)&serv,sizeof(serv));
//设置监听
listen(listenfd, 32);
/**************开始连接*************/
//用sockaddr_in 保存客户端的信息
sockaddr_in clie;
while(1){
socklen_t cliaddr_len = sizeof(clie);
connfd = accept(listenfd,(struct sockaddr*)&clie, &cliaddr_len);
//接受前端传来的数据
char buff[1024];
//str用于保存转换后的ip地址
char str[1024] = { 0 };
printf("received from %s at port %d\n",
inet_ntop(AF_INET,&clie.sin_addr,str,sizeof(str),ntohs(clie.sin_port));
//对于buff操作,do something
//用write把buf重新操作后的数据返回给客户端
write(connfd, buf ,n);
close(connfd);
}
客户端代码:
//创建socket
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
//保存服务端的IP和端口信息
memset(&servaddr, 0 , sizeof(servaddr));
struct sockaddr_in servaddr;
servaddr.sin_family = AF_INET;
//本地服务器,并转换为相应的字节序
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
servaddr.sin_port = htons(SERV_PORT);
//connect
connect(sockfd,(struct sockaddr*)&servaddr, sizeof(servaddr));
//向服务器发送数据
while(1){
char buf[1024];
//从输入流中接受数据
fgets(buf, sizeof(buf), stdin);
//向服务器发送数据
write(sockfd, buf , strlen(buf));
//接受服务器的数据
read(sockfd, buf , sizeof(buf));
//对buf的结果打印
}
close(sockfd);