本篇我们来聊一聊linux编程中的网络编程,还是那句话,学一个东西之前我们必须要知道为什么要学它,它的作用是什么。在我们之前的文章中我们讲了进程间的通信等,进程间通信是基于linux内核的,也就是说不能实现多主机间的通信,而网络可以,这就是为什么我们要学习网络编程。而讲到网络我们就必须与其地址和数据扯上关系,这里所谓的地址就是IP地址和端口号,数据就是其使用的协议,可以理解为数据格式。
在linux系统编程中网络编程是使用socket(套接字),其是用于描述地址和端口,是一个通信链的句柄,应用程序通过socket向网络发出请求或者回应。
一、网络编程前的基础
1.TCP/UDP对比
1、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接;
2、TCP提供可靠的服务,也就是说通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付;
3、TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的,UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等);
4、每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信;
5、TCP首部开销20字节;UDP的首部开销小,只有8字节;
6、TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道;
2.端口号的作用
一台拥有IP地址的主机可以提供许多服务,比如Web服务、FTP服务、SMTP服务等,这些服务完全可以通过一个IP地址来实现。那么主机是怎么样区分不同的网络服务呢?显然不能只靠IP地址,因为IP地址与网络服务关系是一对多的关系。
实际上主机是通过“IP地址+端口号”来区分不同的服务的。端口提供了一种访问通道,服务器一般都是通过知名端口号来识别的。例如对于每个TCP/IP实现来说,FTP服务器的TCP端口号都是21,每个Telnet服务器的TCP端口号都是23,每个TFTP(简单文件传送协议)服务器的UDP端口号都是69。
3.字节序
字节序是指多字节数据在计算机内存中存储或网络传输时各字节的存储顺序。常见序有Little endian(小端字节序)和Big endian(大端字节序)。要注意的是网络字节序是大端字节序。
举个例子:将0x1234abcd写入到以0x0000开始的内存中,其大端:0x0000-0x12,0x0001-0x34,0x0002-0xab,0x0003-0xcd;其小端:0x0000-0xcd,0x0001-0xab,0x0002-0x34,0x0003-0x12。
字节序转换api:
#include <netinet/in.h>
uint16_t htons(uint16_t host16bitvalue); //返回网络字节序的值
uint32_t htonl(uint32_t host32bitvalue); //返回网络字节序的值
uint16_t ntohs(uint16_t net16bitvalue); //返回主机字节序的值
uint32_t ntohl(uint32_t net32bitvalue); //返回主机字节序的值
其中h代表host,n代表net,s代表short,l代表long。
二、Socket服务器和客户端的开发步骤
服务器开发:1.创建套接字(socket)— 2.为套接字添加信息(IP地址和端口号)(bind)— 3.监听网络连接(listen)— 4.监听到有客户端接入,接受一个连接(accept)— 5.数据交互(read、write)— 6.关闭套接字,断开连接(close)
客户端开发:1.创建套接字(socket)— 2.连接服务器(connect)— 3.数据交互(read、write)— 4.关闭套接字,断开连接(close)
三、相关api介绍
连接协议(socket):
函数原型:int socket(int domain, int type, int protocol);
参数1:指明所使用的协议,通常为AF_INET,表示互联网协议族(TCP/IP协议族);
(AF_INET—IPv4因特网域、AF_INET6—IPv6因特网域、AF_UNIX—Unix域、AF_ROUTE—路由套接字、AF_KEY—密钥套接字、AF_UNSPEC—未指定)
参数2:指定socket的类型;
(SOCK_STREAM:流式套接字提供可靠的、面向连接的通信流;使用TCP协议,保证了数据传输的正确性和顺序性;SOCK_DGRAM:数据报套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,而且不保证是可靠的、无差错的。它使用UDP协议;SOCK_RAM:允许程序使用底层协议,原始套接字允许对底层协议如IP或ICMP进行直接访问,功能强大但使用不便,用于协议的开发)
参数3:通常赋值0;
(0:选择type类型对应的默认协议;IPPROTO_TCP—TCP协议;IPPROTO_UDP—UDP协议;IPPROTO_SCTP—SCTP协议;IPPROTO_TIPC—TIPC协议)
成功返回该socket的文件描述符,否则返回-1;
绑定IP地址和端口号(bind):
函数原型:int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
参数1:是一个socket描述符;
参数2:结构体指针,包含协议族、端口号、IP地址等;
参数3:结构体大小;
成功返回0,否则返回-1;
这里涉及到IP地址转换问题:我们人眼看到的是字符串,我们要把IP地址转换为网络能识别的格式:
int inet_aton(const char *straddr,struct in_addr *addrp); //字符串转网络格式
char* inet_ntoa(struct in_addr inaddr); //网络格式转字符串
监听设置函数(listen):
函数原型:int listen(int sockfd, int backlog);
参数1:服务器端socket描述符;
参数2:指定在请求队列中允许的最大请求数;
成功返回0,否则返回-1;
服务器接收客户端连接(accept):
函数原型:int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数1:服务器端socket描述符;
参数2:用来返回已连接的客户端的协议地址;
参数3:客户端地址长度;
成功返回一个新的套接字描述符,即已连接的套接字描述符,否则返回-1;
客户端连接服务器(connect):
函数原型:int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
参数1:目的服务器的socket描述符;
参数2:服务器端的IP地址和端口号的地址结构体指针;
参数3:地址长度;
成功返回0,否则返回-1;
数据收发:
函数原型:
1.ssize_t read(int fd, void *buf, size_t count); //读数据
2.ssize_t write(int fd, const void *buf, size_t count); //写数据
3.ssize_t send(int sockfd, const void *buf, size_t len, int flags); //发数据
4.ssize_t recv(int sockfd, void *buf, size_t len, int flags); //收数据
四、网络编程实操
简单服务器搭建及客户端接入:
server.c:
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
//#include <linux/in.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main()
{
int s_fd;
int c_fd;
struct sockaddr_in s_addr;
struct sockaddr_in c_addr;
char readBuf[128] = {0};
int n_read = 0;
char *retmes = "I got your connect";
memset(&s_addr,0,sizeof(struct sockaddr_in));
memset(&c_addr,0,sizeof(struct sockaddr_in));
//1.int socket(int domain, int type, int protocol);
s_fd = socket(AF_INET,SOCK_STREAM,0);
if (s_fd == -1)
{
perror("socket");
exit(-1);
}
s_addr.sin_family = AF_INET;
//uint16_t htons(uint16_t hostshort);
s_addr.sin_port = htons(8968);
//int inet_aton(const char *cp, struct in_addr *inp);
inet_aton("192.168.43.134",&s_addr.sin_addr);
//2.int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
//3.int listen(int sockfd, int backlog);
listen(s_fd,10);
int length = sizeof(struct sockaddr_in);
//4.int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&length);
if (c_fd == -1)
{
perror("accept");
}
//char *inet_ntoa(struct in_addr in);
printf("get connect:%s\n",inet_ntoa(c_addr.sin_addr));
//5.ssize_t read(int fd, void *buf, size_t count);
n_read = read(c_fd,readBuf,128);
if (n_read == -1)
{
perror("read");
}
else
{
printf("get message:%d,%s\n",n_read,readBuf);
}
//6.ssize_t write(int fd, const void *buf, size_t count);
write(c_fd,retmes,strlen(retmes)+1);
return 0;
}
client.c:
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
//#include <linux/in.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main()
{
int c_fd;
struct sockaddr_in c_addr;
char readBuf[128] = {0};
int n_read = 0;
char *mes = "message from client";
memset(&c_addr,0,sizeof(struct sockaddr_in));
//1.int socket(int domain, int type, int protocol);
c_fd = socket(AF_INET,SOCK_STREAM,0);
if (c_fd == -1)
{
perror("socket");
exit(-1);
}
c_addr.sin_family = AF_INET;
//uint16_t htons(uint16_t hostshort);
c_addr.sin_port = htons(8968);
//int inet_aton(const char *cp, struct in_addr *inp);
inet_aton("192.168.43.134",&c_addr.sin_addr);
//2.int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
if (connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr)) == -1)
{
perror("coonnect");
exit(-1);
}
//3.ssize_t write(int fd, const void *buf, size_t count);
write(c_fd,mes,strlen(mes)+1);
//4.ssize_t read(int fd, void *buf, size_t count);
n_read = read(c_fd,readBuf,128);
if (n_read == -1)
{
perror("read");
}
else
{
printf("get message:%d,%s\n",n_read,readBuf);
}
return 0;
}
server.c运行结果:
client运行结果:
服务器先接收到客户端的接入,把客户端IP打印出来,客户端再把其数据发送给服务器,服务器再回应客户端;这里要注意的是accept函数和connect函数中的struct sockaddr结构体换成了struct sockaddr_in了,最后传参的时候需要强转回来。这里再补充一下:查找某些内容包含在哪个头文件的方法:先cd /usr/include/,再grep "struct sockaddr_in {" * -nir。
我们可以把服务器代码改进一下,通过fork函数来让多个客户端接入:
server.c:
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
//#include <linux/in.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc,char **argv)
{
int s_fd;
int c_fd;
struct sockaddr_in s_addr;
struct sockaddr_in c_addr;
char readBuf[128] = {0};
int n_read = 0;
char *retmes = "I got your connect";
memset(&s_addr,0,sizeof(struct sockaddr_in));
memset(&c_addr,0,sizeof(struct sockaddr_in));
s_fd = socket(AF_INET,SOCK_STREAM,0);
if (s_fd == -1)
{
perror("socket");
exit(-1);
}
s_addr.sin_family = AF_INET;
//uint16_t htons(uint16_t hostshort);
s_addr.sin_port = htons(atoi(argv[2]));
//int inet_aton(const char *cp, struct in_addr *inp);
inet_aton(argv[1],&s_addr.sin_addr);
//2.int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
//3.int listen(int sockfd, int backlog);
listen(s_fd,10);
int length = sizeof(struct sockaddr_in);
while(1)
{
//4.int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&length);
if (c_fd == -1)
{
perror("accept");
}
//char *inet_ntoa(struct in_addr in);
printf("get connect:%s\n",inet_ntoa(c_addr.sin_addr));
if (fork() == 0)
{
//5.ssize_t read(int fd, void *buf, size_t count);
n_read = read(c_fd,readBuf,128);
if (n_read == -1)
{
perror("read");
}
else
{
printf("get message:%d,%s\n",n_read,readBuf);
}
//6.ssize_t write(int fd, const void *buf, size_t count);
write(c_fd,retmes,strlen(retmes)+1);
break;
}
}
return 0;
}
client.c:
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
//#include <linux/in.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc,char **argv)
{
int c_fd;
struct sockaddr_in c_addr;
char readBuf[128] = {0};
int n_read = 0;
char *mes = "message from client";
memset(&c_addr,0,sizeof(struct sockaddr_in));
//1.int socket(int domain, int type, int protocol);
c_fd = socket(AF_INET,SOCK_STREAM,0);
if (c_fd == -1)
{
perror("socket");
exit(-1);
}
c_addr.sin_family = AF_INET;
//uint16_t htons(uint16_t hostshort);
c_addr.sin_port = htons(atoi(argv[2]));
//int inet_aton(const char *cp, struct in_addr *inp);
inet_aton(argv[1],&c_addr.sin_addr);
//2.int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
if (connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr)) == -1)
{
perror("coonnect");
exit(-1);
}
//3.ssize_t write(int fd, const void *buf, size_t count);
write(c_fd,mes,strlen(mes)+1);
//4.ssize_t read(int fd, void *buf, size_t count);
n_read = read(c_fd,readBuf,128);
if (n_read == -1)
{
perror("read");
}
else
{
printf("get message:%d,%s\n",n_read,readBuf);
}
return 0;
}
运行结果:
这里服务器一直没退出,一直等待多个客户端接入。
那我们不妨再改进一下:让服务器和客户端进行通信:
server.c:
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
//#include <linux/in.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc,char **argv)
{
int s_fd;
int c_fd;
struct sockaddr_in s_addr;
struct sockaddr_in c_addr;
char readBuf[128] = {0};
int n_read = 0;
//char *retmes = "I got your connect";
//char retmes[128] = {0};
char *retmes = (char *)malloc(18);
memset(&s_addr,0,sizeof(struct sockaddr_in));
memset(&c_addr,0,sizeof(struct sockaddr_in));
s_fd = socket(AF_INET,SOCK_STREAM,0);
if (s_fd == -1)
{
perror("socket");
exit(-1);
}
s_addr.sin_family = AF_INET;
//uint16_t htons(uint16_t hostshort);
s_addr.sin_port = htons(atoi(argv[2]));
//int inet_aton(const char *cp, struct in_addr *inp);
inet_aton(argv[1],&s_addr.sin_addr);
//2.int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
//3.int listen(int sockfd, int backlog);
listen(s_fd,10);
int length = sizeof(struct sockaddr_in);
while(1)
{
//4.int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&length);
if (c_fd == -1)
{
perror("accept");
}
//char *inet_ntoa(struct in_addr in);
printf("get connect:%s\n",inet_ntoa(c_addr.sin_addr));
if (fork() == 0)
{
if (fork() == 0)
{
while(1)
{
memset(retmes,0,sizeof(retmes));
printf("input: ");
gets(retmes);
write(c_fd,retmes,strlen(retmes)+1);
}
}
while(1)
{
memset(readBuf,0,sizeof(readBuf));
//5.ssize_t read(int fd, void *buf, size_t count);
n_read = read(c_fd,readBuf,128);
if (n_read == -1)
{
perror("read");
}
else
{
printf("get message:%d,%s\n",n_read,readBuf);
}
}
break;
}
}
return 0;
}
client.c:
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
//#include <linux/in.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc,char **argv)
{
int c_fd;
struct sockaddr_in c_addr;
char readBuf[128] = {0};
int n_read = 0;
//char *mes = "message from client";
//char mes[128] = {0};
char *mes = (char *)malloc(18);
memset(&c_addr,0,sizeof(struct sockaddr_in));
//1.int socket(int domain, int type, int protocol);
c_fd = socket(AF_INET,SOCK_STREAM,0);
if (c_fd == -1)
{
perror("socket");
exit(-1);
}
c_addr.sin_family = AF_INET;
//uint16_t htons(uint16_t hostshort);
c_addr.sin_port = htons(atoi(argv[2]));
//int inet_aton(const char *cp, struct in_addr *inp);
inet_aton(argv[1],&c_addr.sin_addr);
//2.int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
if (connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr)) == -1)
{
perror("coonnect");
exit(-1);
}
while(1)
{
if (fork() == 0)
{
while(1)
{
memset(mes,0,sizeof(mes));
printf("input: ");
gets(mes);
//3.ssize_t write(int fd, const void *buf, size_t count);
write(c_fd,mes,strlen(mes)+1);
}
}
while(1)
{
memset(readBuf,0,sizeof(readBuf));
//4.ssize_t read(int fd, void *buf, size_t count);
n_read = read(c_fd,readBuf,128);
if (n_read == -1)
{
perror("read");
}
else
{
printf("get message:%d,%s\n",n_read,readBuf);
}
}
}
return 0;
}
运行结果:
好了,以上就是实现网络编程大概知识点,以及结果小项目来练练手,下一篇文章我会通过网络编程来实现FTP服务器。希望对大家有所帮助,共勉。