Linux网络编程
Posted 今天天气眞好
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux网络编程相关的知识,希望对你有一定的参考价值。
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
1.网络知识介绍
1.IP和端口
所有的数据传输,都有三个要素 :源
、目的
、长度
。
所以,在网络传输中需要使用“IP和端口
”来表示源或目的
2.网络传输中的2个对象:server和client
我们经常访问网站,这涉及2个对象:网站服务器,浏览器。网站服务器平时安静地呆着,浏览器主动发起数据请求。网站服务器、浏览器可以抽象成2个软件的概念:server程序、client程序。
3.常用的命令
(1)netstat
命令netstat是用来显示网络的连接,路由表和接口统计等网络的信息.netstat有许多的选项. 我们常用的选项是-na
用来显示详细的网络状态.至于其它的选项我们可以使用帮助手册获得详细的情况.
(2)telnet
telnet是一个用来登录远程的程序,但是我们完全可以用这个程序来调试我们的服务端程序的。比如我们的服务器程序在监听8888端口,我们可以用 telnet localhost 8888
来查看服务端的状况.
(3)ping
ping 程序用来判断网络的状态是否正常,最经常的一个用法是ping 192.168.0.1
表示我们想查看到192.168.0.1的硬件连接是否正常
4.两种传输方式:TCP/UDP
具体的网络分层模型不做介绍
(1)TCP和UDP 原理上的区别
TCP
向它的应用程序提供了面向连接的服务。这种服务有2个特点:可靠传输、流量控制(即发送方/接收方速率匹配)。它包括了应用层报文划分为短报文,并提供拥塞控制机制。
UDP
协议向它的应用程序提供无连接服务。它没有可靠性,没有流量控制,也没有拥塞控制。
(2)为何存在UDP协议
既然TCP提供了可靠数据传输服务,而UDP不能提供,那么TCP是否总是首选呢?
答案是否定的,因为有许多应用更适合用UDP,举个例子:视频通话时,使用UDP,偶尔的丢包、偶尔的花屏时可以忍受的;如果使用TCP,每个数据包都要确保可靠传输,当它出错时就重传,这会导致后续的数据包被阻滞,视频效果反而不好。
使用UDP时,有如下特点:
a.关于何时发送什么数据控制的更为精细
采用UDP时只要应用进程将数据传递给UDP,UDP就会立即将其传递给网络层。而TCP有重传机制,而不管可靠交付需要多长时间。但是实时应用通常不希望过分的延迟报文段的传送,且能容忍一部分数据丢失。
b.无需建立连接,不会引入建立连接时的延迟。
c.无连接状态,能支持更多的活跃客户。
d.分组首部开销较小。
(3)TCP/UDP网络通信大概交互图
TCP:
UDP:
2.网络编程主要函数介绍
1 socket函数
int socket(int domain, int type,int protocol);
此函数用于创建一个套接字。
domain
是网络程序所在的主机采用的通讯协族(AF_UNIX和AF_INET等)。
AF_UNIX只能够用于单一的Unix 系统进程间通信,而AF_INET是针对Internet的,因而可以允许远程通信使用。
type
是网络程序所采用的通讯协议(SOCK_STREAM,SOCK_DGRAM等)。
SOCK_STREAM表明用的是TCP 协议,这样会提供按顺序的,可靠,双向,面向连接的比特流。
SOCK_DGRAM 表明用的是UDP协议,这样只会提不可靠,无连接的通信。
关于protocol
,由于指定了type,所以这个地方一般只要用0来代替就可以了。
此函数执行成功时返回文件描述符,失败时返回-1,看errno可知道出错的详细情况。
2 bind函数
int bind(int sockfd, struct sockaddr *my_addr, int addrlen);
从函数用于将地址绑定到一个套接字。
sockfd
是由socket函数调用返回的文件描述符。
my_addr
是一个指向sockaddr的指针。
addrlen
是sockaddr结构的长度。
sockaddr的定义:
struct sockaddr{
unisgned short as_family;
char sa_data[14];
};
不过由于系统的兼容性,我们一般使用另外一个结构(struct sockaddr_in) 来代替。
sockaddr_in的定义:
struct sockaddr_in{
unsigned short sin_family;
unsigned short sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8];
}
如果使用Internet所以sin_family一般为AF_INET。
sin_addr
设置为INADDR_ANY表示可以和任何的主机通信。
sin_port
是要监听的端口号。
bind将本地的端口同socket返回的文件描述符捆绑在一起.成功是返回0,失败的情况和socket一样。
3.listen函数
int listen(int sockfd,int backlog);
此函数宣告服务器可以接受连接请求。
sockfd
是bind后的文件描述符。
backlog
设置请求排队的最大长度。当有多个客户端程序和服务端相连时,使用这个表示可以介绍的排队长度。
listen函数将bind的文件描述符变为监听套接字,返回的情况和bind一样。
4 accept函数
int accept(int sockfd, struct sockaddr *addr,int *addrlen);
服务器使用此函数获得连接请求,并且建立连接。
sockfd
是listen后的文件描述符。
addr
,addrlen
是用来给客户端的程序填写的,服务器端只要传递指针就可以了, bind,listen和accept是服务器端用的函数。
accept调用时,服务器端的程序会一直阻塞到有一个客户程序发出了连接。 accept成功时返回最后的服务器端的文件描述符,这个时候服务器端可以向该描述符写信息了,失败时返回-1 。
5 connect函数
int connect(int sockfd, struct sockaddr * serv_addr,int addrlen);
可以用connect建立一个连接,在connect中所指定的地址是想与之通信的服务器的地址。
sockfd
是socket函数返回的文件描述符。
serv_addr
储存了服务器端的连接信息,其中sin_add是服务端的地址。
addrlen
是serv_addr的长度
connect函数是客户端用来同服务端连接的.成功时返回0,sockfd是同服务端通讯的文件描述符,失败时返回-1。
6 send函数
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
sockfd
指定发送端套接字描述符;
buf
指明一个存放应用程序要发送数据的缓冲区;
len
指明实际要发送的数据的字节数;
flags
一般置0。
客户或者服务器应用程序都用send函数来向TCP连接的另一端发送数据
7 recv函数
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
sockfd
指定接收端套接字描述符;
buf
指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;
len
指明buf的长度;
flags
一般置0。
客户或者服务器应用程序都用recv函数从TCP连接的另一端接收数据。
8 recvfrom函数
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
recvfrom通常用于无连接套接字,因为此函数可以获得发送者的地址。
src_addr
是一个struct sockaddr类型的变量,该变量保存源机的IP地址及端口号。
addrlen
常置为sizeof (struct sockaddr)。
9 sendto函数
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
sendto和send相似,区别在于sendto允许在无连接的套接字上指定一个目标地址。
dest_addr
表示目地机的IP地址和端口号信息,
addrlen
常常被赋值为sizeof (struct sockaddr)。
sendto
函数也返回实际发送的数据字节长度或在出现发送错误时返回-1。
3.TCP编程
1.client.c
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
/* socket
* connect
* send/recv
*/
#define SERVER_PORT 8888
int main(int argc, char **argv)
{
int iSocketClient;
struct sockaddr_in tSocketServerAddr;
int iRet;
unsigned char ucSendBuf[1000];
int iSendLen;
if (argc != 2)
{
printf("Usage:\\n");
printf("%s <server_ip>\\n", argv[0]);
return -1;
}
iSocketClient = socket(AF_INET, SOCK_STREAM, 0);
tSocketServerAddr.sin_family = AF_INET;
tSocketServerAddr.sin_port = htons(SERVER_PORT); /* host to net, short */
//tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
if (0 == inet_aton(argv[1], &tSocketServerAddr.sin_addr))
{
printf("invalid server_ip\\n");
return -1;
}
memset(tSocketServerAddr.sin_zero, 0, 8);
iRet = connect(iSocketClient, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
if (-1 == iRet)
{
printf("connect error!\\n");
return -1;
}
while (1)
{
if (fgets(ucSendBuf, 999, stdin))
{
iSendLen = send(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0);
if (iSendLen <= 0)
{
close(iSocketClient);
return -1;
}
}
}
return 0;
}
2.server.c
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
/* socket
* bind
* listen
* accept
* send/recv
*/
#define SERVER_PORT 8888
#define BACKLOG 10
int main(int argc, char **argv)
{
int iSocketServer;
int iSocketClient;
struct sockaddr_in tSocketServerAddr;
struct sockaddr_in tSocketClientAddr;
int iRet;
int iAddrLen;
int iRecvLen;
unsigned char ucRecvBuf[1000];
int iClientNum = -1;
signal(SIGCHLD,SIG_IGN);
iSocketServer = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == iSocketServer)
{
printf("socket error!\\n");
return -1;
}
tSocketServerAddr.sin_family = AF_INET;
tSocketServerAddr.sin_port = htons(SERVER_PORT); /* host to net, short */
tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
memset(tSocketServerAddr.sin_zero, 0, 8);
iRet = bind(iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
if (-1 == iRet)
{
printf("bind error!\\n");
return -1;
}
iRet = listen(iSocketServer, BACKLOG);
if (-1 == iRet)
{
printf("listen error!\\n");
return -1;
}
while (1)
{
iAddrLen = sizeof(struct sockaddr);
iSocketClient = accept(iSocketServer, (struct sockaddr *)&tSocketClientAddr, &iAddrLen);
if (-1 != iSocketClient)
{
iClientNum++;
printf("Get connect from client %d : %s\\n", iClientNum, inet_ntoa(tSocketClientAddr.sin_addr));
if (!fork())
{
/* 子进程的源码 */
while (1)
{
/* 接收客户端发来的数据并显示出来 */
iRecvLen = recv(iSocketClient, ucRecvBuf, 999, 0);
if (iRecvLen <= 0)
{
close(iSocketClient);
return -1;
}
else
{
ucRecvBuf[iRecvLen] = '\\0';
printf("Get Msg From Client %d: %s\\n", iClientNum, ucRecvBuf);
}
}
}
}
}
close(iSocketServer);
return 0;
}
4.UDP编程
1.client.c
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
/* socket
* connect
* send/recv
*/
#define SERVER_PORT 8888
int main(int argc, char **argv)
{
int iSocketClient;
struct sockaddr_in tSocketServerAddr;
int iRet;
unsigned char ucSendBuf[1000];
int iSendLen;
int iAddrLen;
if (argc != 2)
{
printf("Usage:\\n");
printf("%s <server_ip>\\n", argv[0]);
return -1;
}
iSocketClient = socket(AF_INET, SOCK_DGRAM, 0);
tSocketServerAddr.sin_family = AF_INET;
tSocketServerAddr.sin_port = htons(SERVER_PORT); /* host to net, short */
//tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
if (0 == inet_aton(argv[1], &tSocketServerAddr.sin_addr))
{
printf("invalid server_ip\\n");
return -1;
}
memset(tSocketServerAddr.sin_zero, 0, 8);
#if 0
iRet = connect(iSocketClient, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
if (-1 == iRet)
{
printf("connect error!\\n");
return -1;
}
#endif
while (1)
{
if (fgets(ucSendBuf, 999, stdin))
{
#if 0
iSendLen = send(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0);
#else
iAddrLen = sizeof(struct sockaddr);
iSendLen = sendto(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0,
(const struct sockaddr *)&tSocketServerAddr, iAddrLen);
#endif
if (iSendLen <= 0)
{
close(iSocketClient);
return -1;
}
}
}
return 0;
}
2.server.c
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
/* socket
* bind
* sendto/recvfrom
*/
#define SERVER_PORT 8888
int main(int argc, char **argv)
{
int iSocketServer;
int iSocketClient;
struct sockaddr_in tSocketServerAddr;
struct sockaddr_in tSocketClientAddr;
int iRet;
int iAddrLen;
int iRecvLen;
unsigned char ucRecvBuf[1000];
int iClientNum = -1;
iSocketServer = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == iSocketServer)
{
printf("socket error!\\n");
return -1;
}
tSocketServerAddr.sin_family = AF_INET;
tSocketServerAddr.sin_port = htons(SERVER_PORT); /* host to net, short */
tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
memset(tSocketServerAddr.sin_zero, 0, 8);
iRet = bind(iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
if (-1 == iRet)
{
printf("bind error!\\n");
return -1;
}
while (1)
{
iAddrLen = sizeof(struct sockaddr);
iRecvLen = recvfrom(iSocketServer, ucRecvBuf, 999, 0, (struct sockaddr *)&tSocketClientAddr, &iAddrLen);
if (iRecvLen > 0)
{
ucRecvBuf[iRecvLen] = '\\0';
printf("Get Msg From %s : %s\\n", inet_ntoa(tSocketClientAddr.sin_addr), ucRecvBuf);
}
}
close(iSocketServer);
return 0;
}
5.服务器和客户机的信息函数
1 字节转换函数
在网络上面有着许多类型的机器,这些机器在表示数据的字节顺序是不同的, 比如i386芯片是低字节在内存地址的低端,
高字节在高端,而alpha芯片却相反. 为了统一起来,在Linux下面,有专门的字节转换函数.
unsigned long int htonl(unsigned long int hostlong)
unsigned short int htons(unisgned short int hostshort)
unsigned long int ntohl(unsigned long int netlong)
unsigned short int ntohs(unsigned short int netshort)
在这四个转换函数中,h 代表host
, n 代表 network
.s 代表short
l 代表long
第一个函数的意义是将本机器上的long数据转化为网络上的long. 其他几个函数的意义也差不多.
2 字符串的IP和32位的IP转换.
在网络上面我们用的IP都是数字加点(192.168.0.1)构成的, 而在struct in_addr结构中用的是32位的IP,
我们上面那个32位IP(C0A80001)是的192.168.0.1 为了转换我们可以使用下面两个函数
int inet_aton
(const char *cp,struct in_addr *inp)
char *inet_ntoa
(struct in_addr in)
函数里面 a 代表 ascii n 代表network.第一个函数表示将a.b.c.d的IP转换为32位的IP,
存储在 inp指针里面.第二个是将32位IP转换为a.b.c.d的格式.
以上是关于Linux网络编程的主要内容,如果未能解决你的问题,请参考以下文章
VSCode自定义代码片段14——Vue的axios网络请求封装