一、程序
首先上程序
client端的程序
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#define SERVER_PORT 5001 // 本地字节序
#define SERVER_HOST "192.168.0.43"
#define SERVER_BACKLOG 5
#define SERVER_QUIT "q"
#define SERVER_SIZE 32
int test_client()
{
printf("%s %d \n", __func__, __LINE__);
int fd = -1;
struct sockaddr_in addr_in;
char buf[SERVER_SIZE];
/*
AF_INET:IPV4
SOCK_STREAM:TCP
*/
// 1、创建socket 得到fd
fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0)
{
printf("%s %d fd<0\n", __func__, __LINE__);
return 0;
}
/*
2、连接
*/
memset(&addr_in, 0, sizeof(addr_in)); // 将变量addr_in置0
addr_in.sin_port = htons(SERVER_PORT); // 将端口号从本地字节序转换为网络字节序
addr_in.sin_family = AF_INET;
addr_in.sin_addr.s_addr = inet_addr(SERVER_HOST); // 将主机从点分形式转换为32字节
int reccon = connect(fd, (struct sockaddr *)&addr_in, sizeof(addr_in));
if (reccon < 0)
{
printf("%s %d reccon<0\n", __func__, __LINE__);
return 0;
}
// 读写
while (1)
{
memset(buf, 0, SERVER_SIZE);
char *rec_p = fgets(buf, SERVER_SIZE - 1, stdin);
int recwrite = write(fd, buf, strlen(buf));
if (recwrite > 0)
{
printf("%s %d buf = %s\n", __func__, __LINE__, buf);
// int recstr = strcmp(buf, SERVER_QUIT);
int recstr = strncasecmp(buf, SERVER_QUIT, strlen(SERVER_QUIT));
printf("%s %d recstr = %d\n", __func__, __LINE__, recstr);
if (0 == recstr)
{
printf("%s %d q======\n", __func__, __LINE__);
break;
}
}
usleep(100);
}
close(fd);
printf("%s %d xxxxxxxxxxxxxx\n", __func__, __LINE__);
return 0;
}
server端的程序
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#define SERVER_PORT 5001 // 本地字节序
#define SERVER_HOST "192.168.0.43"
#define SERVER_BACKLOG 5
#define SERVER_QUIT "q"
#define SERVER_SIZE 32
int test_server()
{
printf("%s %d \n", __func__, __LINE__);
int fd = -1;
struct sockaddr_in addr_in;
char buf[SERVER_SIZE];
/*
AF_INET:IPV4
SOCK_STREAM:TCP
*/
// 1、创建socket 得到fd
fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0)
{
printf("%s %d fd<0\n", __func__, __LINE__);
return 0;
}
// 2、绑定
memset(&addr_in, 0, sizeof(addr_in)); // 将变量addr_in置0
addr_in.sin_port = htons(SERVER_PORT); // 将端口号从本地字节序转换为网络字节序
addr_in.sin_family = AF_INET;
// int rec = inet_pton(AF_INET,SERVER_HOST,(void*)&addr_in.sin_addr.s_addr);
// if(rec!= 1){
// printf("%s %d rec!= 1\n", __func__, __LINE__);
// return 0;
// }
addr_in.sin_addr.s_addr = inet_addr(SERVER_HOST); // 将主机从点分形式转换为32字节
int rec = bind(fd, (struct sockaddr *)&addr_in, sizeof(addr_in));
if (rec < 0)
{
printf("%s %d rec<0\n", __func__, __LINE__);
return 0;
}
// 3、listen() 将主动套接字转换为被动套接字
int reclisten = listen(fd, SERVER_BACKLOG); // 允许正在进行连接的客户端数目
if (reclisten < 0)
{
printf("%s %d reclisten<0\n", __func__, __LINE__);
return 0;
}
// 4、阻塞等待客户端连接请求
int newfd = accept(fd, NULL, NULL);
if (newfd < 0)
{
printf("%s %d newfd<0\n", __func__, __LINE__);
return 0;
}
// 5、读写
// 和最新的newfd进行通信
while (1)
{
memset(buf, 0, SERVER_SIZE);
int recread = read(newfd, buf, SERVER_SIZE - 1);
if (recread > 0)
{
printf("%s %d buf = %s\n", __func__, __LINE__, buf);
if (!strncasecmp(buf, SERVER_QUIT, strlen(SERVER_QUIT)))
{
printf("%s %d q======\n", __func__, __LINE__);
break;
}
}
usleep(100);
}
close(fd);
close(newfd);
printf("%s %d xxxxxxxxxxxxxx\n", __func__, __LINE__);
return 0;
}
使用方法
1、新建两个程序,分别引用两个函数,先执行server端的程序,再执行client端的程序
2、实现功能:当client和sever连接成功后,从client输入什么都会传输给server端,当输入第一个字母为q时 两端程序都会退出
3、特别注意:需要修改SERVER_HOST 为自己主机地址
4、本程序编写的环境,如果时windows下执行可能需要修改头文件什么的,耐心一点看就好
gcc -v
gcc version 11.3.0 (Ubuntu 11.3.0-1ubuntu1~22.04.1)
cmake -version
cmake version 3.22.1
make -v
GNU Make 4.3
基本顺序
客户端的函数主要就是socket和connect,服务器端主要就是socket,bing,listen和accept函数,write和read函数和操作文件读写时说一样的使用方法。
函数细节
int socket(int domain, int type, int protocol)
使用man socket可以查看函数细节
需要包含的头文件:
#include <sys/types.h>
#include <sys/socket.h>
函数原型:
int socket(int domain, int type, int protocol);
domain:指定网络通信使用的协议族或地址族,常见的值包括
AF_INET : IPv4 Internet protocols
AF_INET6 : IPv6 Internet protocols
type:指定套接字的类型,常见的值包括
SOCK_STREAM:提供面向连接的、可靠的、双向字节流的服务,使用TCP协议。
SOCK_DGRAM:提供无连接的、不可靠的、具有固定最大长度的消息传递服务,使用UDP协议。
SOCK_RAW:提供原始套接字访问,可以直接发送和接收底层协议的数据包。
protocol:指定具体的传输协议,一般情况下可以设置为0,由操作系统自动选择合适的协议。
返回值:
创建成功时,返回一个新的套接字描述符(socket descriptor),用于后续的网络通信操作。
创建失败时,返回 -1,并设置全局变量 errno 表示具体的错误类型。
这个地方的返回就是相当于文件描述符,要记得记录,后面需要一直用到
重点:int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数说明:
sockfd:要绑定的套接字描述符。
addr:指向用于绑定的地址结构体的指针,类型为 struct sockaddr*。
addrlen:地址结构体的长度。
这个函数首先要关注const struct sockaddr *addr,将代码跳转过去可以看到sockaddr 的定义如下所示,但是在实际的应用当中我们却不定义struct sockaddr 类型的变量,而是定义struct sockaddr_in的变量,最后进行强制类型转换填入函数bind。
解释原因:
bind() 函数的 addr 参数要求传入一个指向 struct sockaddr 结构体的指针,以指定要绑定的地址信息。但是,由于struct sockaddr是一个通用的结构体类型,只包含地址族和地址数据,并没有特定协议族的详细信息。因此,在实际应用中,我们使用具体的地址结构体类型来填充 struct sockaddr
struct sockaddr
{
__SOCKADDR_COMMON (sa_); /* Common data: address family and length. */
char sa_data[14]; /* Address data. */
};
例如,在使用IPv4协议(AF_INET)时,我们使用 struct sockaddr_in 结构体来表示IPv4地址信息,它定义如下:
struct sockaddr_in {
sa_family_t sin_family; // 地址族(协议族)
in_port_t sin_port; // 端口号
struct in_addr sin_addr; // IPv4地址
unsigned char sin_zero[8]; // 填充字段(通常设置为0)
};
在实际应用中,我们通常会创建一个 struct sockaddr_in 类型的变量,填充需要的地址信息,然后将其转换为 struct sockaddr* 类型的指针传递给 bind() 函数。这样可以确保在绑定时使用了特定协议族的地址结构体,以便正确地指定地址和端口
uint16_t htons(uint16_t hostshort);
htons() 是一个函数,用于将一个16位无符号整数(hostshort)从主机字节序(Host Byte Order)转换为网络字节序(Network Byte Order)
在计算机网络中,字节序指定了多字节数据在内存中的存储顺序。主机字节序是指当前主机所使用的字节序,而网络字节序是一种标准的字节序,用于在不同主机之间进行数据交换。
htons() 函数的参数 hostshort 是一个16位无符号整数(uint16_t 类型),表示需要转换的主机字节序的值。
函数的返回值是一个16位无符号整数,表示转换为网络字节序后的值
该函数名中的 “htons” 表示 “host to network short”,其中 “short” 表示16位整数类型。类似的函数还有 htonl() 用于转换32位整数。
例如,假设我们有一个16位整数 value 需要在网络中传输,我们可以使用 htons() 函数将其转换为网络字节序:
uint16_t value = 5001; // 假设需要转换的主机字节序的端口号
uint16_t networkValue = htons(value); // 将主机字节序转换为网络字节序
in_addr_t inet_addr(const char *cp);
inet_addr() 是一个函数,用于将一个点分十进制字符串形式的IPv4地址转换为32位无符号整数(in_addr_t 类型)的网络字节序表示。
函数的参数 cp 是一个指向以空字符结尾的字符串的指针,该字符串表示一个IPv4地址。
函数的返回值是一个32位无符号整数,表示转换后的网络字节序的IPv4地址。如果转换失败,则返回 INADDR_NONE。
inet_addr()
函数将点分十进制表示的IPv4地址转换为网络字节序的32位整数表示。IPv4地址由四个用点分隔的十进制数表示(例如:192.168.0.1)。该函数将这种字符串形式的IPv4地址转换为二进制格式,以便在网络中进行传输和处理。
以下是使用 inet_addr() 函数的示例:
const char *ipAddress = "192.168.0.1";
in_addr_t addr = inet_addr(ipAddress);
int listen(int sockfd, int backlog);
listen() 函数用于将一个套接字(socket)设置为被动模式,并开始监听传入的连接请求。
参数说明:
sockfd:要监听的套接字描述符。
backlog:定义了连接请求队列的最大长度。
在网络编程中,当一个套接字处于监听状态时,它可以接收传入的连接请求。listen()
函数将套接字设置为监听模式,并指定传入连接请求的最大队列长度。
调用 listen() 函数时,套接字必须先通过 bind() 函数绑定到一个具体的地址和端口上。然后,使用 listen()
函数来指定该套接字的监听参数。
在监听状态下,当有新的连接请求到达时,操作系统会将其放入连接请求队列中。队列的长度由 backlog
参数指定,超过队列长度的连接请求将被拒绝
需要注意的是,listen() 函数仅适用于面向连接的套接字(如 TCP 套接字)。对于无连接的套接字(如 UDP 套接字),不需要调用 listen() 函数。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
accept() 函数用于从已监听的套接字中接受一个传入的连接请求,并创建一个新的套接字来与客户端进行通信。
参数说明:
sockfd:已监听的套接字描述符。
addr:指向用于存储客户端地址信息的结构体的指针,类型为 struct sockaddr*。
addrlen:指向一个整数值,表示 addr 结构体的长度的指针。 flags:可选的标志参数,通常设置为0。
函数在调用时会阻塞,直到有客户端发起连接请求。当一个连接请求到达监听的套接字时,
函数会接受该请求,并创建一个新的套接字来与客户端进行通信。该新创建的套接字用于与客户端之间的数据传输。
函数返回值是一个新创建的套接字描述符,用于与客户端进行通信。
如果出现错误,返回值为 -1,并设置相应的错误码。
在上面的代码当中 第二个和低三个参数都填了NULL,是目前代码当中没有用到连接client的信息,如果需要用到,和前面定义server端 的方式类似
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
connect() 函数用于在套接字上建立与远程服务器的连接。
参数说明:
sockfd:套接字描述符,用于标识要进行连接的套接字。 addr:指向远程服务器地址信息的结构体指针,类型为 struct sockaddr*。
addrlen:远程服务器地址信息结构体的长度。
调用 connect()函数时,客户端套接字尝试与远程服务器建立连接。需要在调用 connect() 函数之前,先使用 bind()函数将套接字绑定到本地地址和端口。
函数返回值:
如果连接建立成功,返回值为0。
如果连接建立失败,返回值为-1,并设置相应的错误码。
其他函数
在代码当中使用到的write,read,close和操作文件时的函数基本一致,这里不赘述。