本节讲解需要相关的网络基础知识,如果缺少相关知识,请先学习:Linux网络编程之网络基础 和 Linux网络编程之网络基础2 。
先打开我们之前讲过的Tcp模型作为参考:
创建socket:int socket(int domain, int type, int protocal);
参数1:域 AF_INET(ipv4) AF_INET6 (ipv6)
参数2:类型 SOCK_DGRAM(udp协议) SOCK_STREAM (tcp协议)
参数3:默认0即可
绑定地址:int bind(int sockfd, const struct sockaddr* addr, socklen_t len);
参数1:socket描述符
参数2:地址结构体
//通用的地址【此处所用的结构体】
struct sockaddr{
sa_family_t sa_family; //协议族
char sa_data[14]; //地址
}
//ipv4地址【ipv4专用,我们平常所用的最多】
struct sockaddr_in{
short int sin_family; //协议族
unsigned short int sin_port; //端口号
struct in_addr sin_addr; // IP地址【结构体原型在下面】
unsigned char sin_zero[8]; //填充
}
struct in_addr{
unsigned long s_addr; // IP地址 4字节整数
}
补充1:字符串形式的IP地址与整数形式的IP地址转化
从上面可以看到,我们用的地址是整数型的;而我们平常在现实生活中所看到的地址:例如:192.168.1.1是字符串型的;
地址转化函数:
in_addr_t inet_addr(const char* cp); //将192.168.1.1转化为整数型的IP地址(网络字节序)
举例:in_addr.saddr = inet_addr(“192.168.1.1” );
char *inet_ntoa (struct in_addr) //将整数形式的IP地址转化为字符串形式的IP地址
补充2:网络字节序
补充2.1:大端和小端模式
要想知道网络字节序是什么?首先需要知道大端和小端模式;简而言之:大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中 ;小端模式,是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中 ;
具体知识请参考https://blog.csdn.net/litter_driver777/article/details/51636311。
我们自己电脑可能是大端,也可能是小端的;同样,别人的电脑也有可能是大端或者小端的,所以在网络上传输数据时,我们就需要统一他们的大小端模式。
规定网络中传输的是大端模式,所以网络字节序是大端模式的;
主机字节序和网络字节序转化函数:
uint32_t htonl(uint32_t hostlong); //将32位的数据从主机字节序转换为网络字节序
//举例: in_addr.saddr = htonl(INADDR_ANY)
uint16_t htons(uint16_t hostshort); //将16位的数据从主机字节序转换为网络字节序
uint32_t ntohl(uint32_t netlong); //将32位的数据从网络字节序转换为主机字节序
uint16_t ntohs(uint16_t netshort); //将16位的数据从网络字节序转换为主机字节序
监听:int listen(int sockfd, int backlog);
参数1:socket描述符
参数2:backlog 允许连接的客户机的数目
等待连接:int accept(int sockfd, struct sockaddr* reStrict addr, socklen_t* restrict len);
参数1:socket描述符
参数2:客户机的地址(接收到的)
参数3:地址长度的指针(注意:是指针哦,不是整形)
返回值:新的套接字的fd(记住了:以后收发用这个新的sockfd哦!)
发送:ssize_t send(int sockfd, const void* buf, size_t nbytes, int flags);
参数1:新的套接字的fd(accept的返回值)
参数2:要发送的数据
参数3:数据的长度
参数4:默认为0即可
接收:ssize_t recv(int sockfd, void* buf, size_t nbytes, int flags);
关闭连接close():
连接服务器:connect(int sockfd, const struct sockaddr* addr, socklen_t len);
参数1:socket描述符
参数2:要连接的服务器的地址
参数3:地址长度—整形
其余函数参考服务器端的讲解。
服务器code:
#include
#include
#include
#include
#define portnum 3333
int main()
{
int sockfd;
int new_fd;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
char buffer[128];
int nByte;
int sin_size;
//1.创建套接字
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
printf("create socket error!\n");
exit(1);
}
//2.1设置要绑定的地址
bzero(&server_addr, sizeof(struct sockaddr_in));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(portnum); //字节序(大小端)
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
//2.绑定地址
bind(sockfd,(struct sockaddr*)(&server_addr),sizeof(struct sockaddr));
//3.监听端口
listen(sockfd,5);
while(1)
{
//4.等待连接
sin_size = sizeof(struct sockaddr);
new_fd = accept(sockfd,(struct sockaddr*)(&client_addr),&sin_size);//注意:第3个参数为socklen_t*(整形的指针)
printf("server get connection from %s\n",inet_ntoa(client_addr.sin_addr)); //将整数型的IP地址转化为字符型(192.168.1.1)
//5.接收数据
nByte = recv(new_fd, buffer, 128, 0);
buffer[nByte] = '\0';
printf("server reciivd : %s\n", buffer);
//6.结束连接
close(new_fd);
}
close(sockfd);
return 0;
}
客户端code:
#include
#include
#include
#include
#define portnum 3333
int main()
{
struct sockaddr_in server_addr;
char buffer[128];
//1.创建套接字
int sockfd;
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
printf("create socket error!\n");
exit(1);
}
//2.1设置要连接的服务器的地址
bzero(&server_addr, sizeof(struct sockaddr_in));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(portnum);
server_addr.sin_addr.s_addr = inet_addr("192.168.103.100");
//2.连接服务器
if(connect(sockfd,(struct sockaddr*)(&server_addr),sizeof(struct sockaddr)) == -1)
{
printf("connect error\n");
exit(1);
}
//3.发送数据到服务器
printf("please input char:\n");
fgets(buffer,128,stdin);
send(sockfd, buffer, strlen(buffer), 0);
//4.关闭连接
close(sockfd);
return 0;
}
操作流程:先开服务器,再开客户端,客户端提示输入please input char:后,自行输入任意字符串,点enter后发送,可以看到服务器收到了数据。
服务器端:
客户机端: