前言
本篇文章将带大家来正式学习Linux网络编程。
一、客户端与服务端
一般认为服务器是一个长时间运行的程序(既守护程序)他只在相应来自网络请求时才发送网络消息
协议的另一端是客户端程序,比如某些浏览器等,一般和服务器之前的通信大多数都是由客户端发起
一般来说一个客户端每次只能和一个服务器进行通信,但是客户端也可以和多个服务端进行通信,可能在10分钟客户端就和不同的服务器进行通信。
服务器每次能够接收多个客户端程序的连接进行通信。
服务器一次处理多个客户端程序的连接:
客户端与服务端在同一个以太网中进行通信:
二、客户端程序编写
这里我们先编写一个简单客户端程序:
编写程序的步骤:
1.使用man手册查看要包含的头文件:
这里我就不一 一例举了,需要哪些函数的头文件只需要根据man手册中提供的包含进去就可以了。
2.使用socket函数创建TCP套接字
在man手册中找到函数原型:
int socket(int domain, int type, int protocol);
第一个参数含义:
第一个参数代表的是表示Socket使用的协议族,必须指定。这里我们使用IPV4协议选择AF_INET。
第二个参数:
type:表示Socket的类型,必须指定。常用的Socket类型有三种:
SOCK_STREAM:表示使用TCP协议进行可靠的面向连接通信,是网络编程中最常用的Socket类型。
SOCK_DGRAM:表示使用UDP协议进行不可靠的无连接通信,常用于视频和音频流的传输。
SOCK_RAW:表示使用原始数据报协议进行通信,常用于嗅探网络报文。 第三个参数:
这里我们选择SOCK_STREAM,因为我们编写的是TCP客户端程序,UDP后面的文章再进行讲解。
第三个参数;
protocol:表示协议编号,通常为0。这个参数可以让操作系统自己根据前两个参数来选择合适的协议进行通信。
3.指定服务器的IP地址和端口
创建一个sockaddr_in 结构体,这个结构体里面包含了要指定的IP和端口成员。
struct sockaddr_in servaddr;
在对这个结构体进行赋值时首先先使用bzero函数对这个结构体里面的成员变量进行清除。
bzero函数介绍:
void bzero(void *s, size_t n);
bzero()函数有两个参数:第一个参数为要清空的内存块的指针,第二个参数为要清空的内存块的大小,单位是字节。函数作用是将内存块中的所有字节都设置为0。该函数不返回任何值。
bzero()函数是C语言的标准库函数,定义在头文件中,在UNIX和Linux系统中也被广泛应用于网络编程中,用于清空套接字描述符、网络地址等数据结构,以及清零接收和发送缓冲区等。
字节序转换函数介绍:
使用inet_pton函数进行字节序的转换,它用于将点分十进制表示的 IPv4 网络地址或者 IPv6 网络地址转化为相应的二进制格式。
int inet_pton(int af, const char *src, void *dst);
其中,af 参数指定了地址族,它有以下两种类型:
AF_INET:IPv4 地址族。
AF_INET6:IPv6 地址族。
src 参数是一个指向存储点分十进制表示的 IPv4 或者 IPv6 地址的字符串的指针,例如:“192.168.1.1” 或 “2001:0db8:0000:0000:0000:ff00:0042:8329”。
dst 参数是一个指向存储转换后二进制格式 IP 地址的指针,通常是一个 in_addr 或 in6_addr 结构体的地址。如果转换成功,则该函数返回 1,否则返回 0(例如:src 格式不正确)。
4连接服务端
使用connect函数和指定的服务端进行连接。
man手册中connect函数的用法:
sockfd就是我们前面创建出来的套接字。
struct sockaddr是我们指定的服务端信息结构体。
addrlen是结构体的大小。
5获取和发送数据
这里我们使用read函数进行数据的读取。学过Linux网络编程的小伙伴可能会问到了为什么不使用recv函数进行数据的读取呢?
因为这里的套接字socket也想当于是一个文件描述符,所以可以使用read函数进行数据的读取。
6.关闭客户端
使用close函数关闭创建的套接字即可。
#include <unistd.h> #include <stdio.h> #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> #include <strings.h> #include <arpa/inet.h> int main(int argc, char **argv) { int sockfd = 0; int n = 0; char recv_buf[64]; struct sockaddr_in servaddr; if(argc != 2) { printf("parameter is err\n"); } if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { printf(" socket is err\n "); } bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(8888); if(inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0) { printf("inet_pton is err\n"); } if(connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) { printf("connect is err\n"); } while ((n = read(sockfd, &recv_buf, 64)) > 0) { printf("read bytes is %d\n", n); recv_buf[n] = 0; for(int i = 0; i < n; i++) { printf("%c", recv_buf[i]); } printf("\n"); } if(n < 0) { printf("read err\n"); } close(sockfd); return 0; }
三、程序的编译和运行
使用gcc命令进行编译:
运行程序并使用网络调试助手进行验证;
这里使用NetAssist这个网络调试助手进行调试:
将服务器的地址传给编写好的客户端程序进行连接:
发送数据:
程序测试通过:
总结
本篇文章首先对客户端和服务端进行了一个简要的介绍,然后编写了一个客户端程序带大家了解了具体的编程步骤。下一篇文章将带大家编写服务端的程序,使用自己编写的服务端和客户端进行通信。