文章目录
一、Socket编程
1.1 基本概念
在Linux环境下,用于表示进程间网络通信的
特殊文件类型
(IP和port);
- 本质:为内核借助缓冲区形成的
伪文件
。- 即是文件,则可用
文件描述符
来引用;进而,在Linux中封装成与读写文件操作一致的接口;- 若要建立连接,则两个进程必须
各自
有一个socket标识【一对一】;- 在网络通信中,套接字一定是
成对
出现的。一端的发送缓冲区
对应对端的接收缓冲区
。我们使用同一个文件描述符索发送
缓冲区和接收
缓冲区。
网络编程接口
1.2 socket常用函数
1.2.1 socket
/**
* # include <sys/types.h>
* # include <sys/socket.h>
* int socket(int domain, int type, int protocol)
* -------
* func:创建通信端点(服务端)并返回一个描述符;
* param domain:指定通信域;选择用于通信的协议族;
AF_UNIX, AF_LOCAL 本地通信
AF_INET IPv4
AF_INET6 IPv6
AF_IPX IPX
AF_NETLINK Kernel user interface device
AF_X25 ITU-T X.25 / ISO-8208 protocol
AF_AX25 Amateur radio AX.25 protocol
AF_ATMPVC Access to raw ATM PVCs
AF_APPLETALK Appletalk
AF_PACKET Low level packet interface
* param type:指定通信语义;
SOCK_STREAM 提供有序的、可靠的、双向的、基于连接的字节流。 可能支持带外数据传输机制;
SOCK_DGRAM 支持数据报(固定最大长度的无连接、不可靠消息);
SOCK_SEQPACKET 为最大固定长度的atagram提供一个顺序的、可靠的、基于双向连接的数据传输路径; 消费
者需要在每次输入系统调用时读取整个数据包;
SOCK_RAW 提供原始网络协议访问;
SOCK_RDM 提供不保证顺序的可靠数据报层;
SOCK_NONBLOCK 在新打开的文件描述中设置O_NONBLOCK文件状态标志。 使用这个标志可以节省对fcntl(2)
的额外调用来实现相同的结果;
* param protocol:当只有一个协议支持指定的套接字类型时,为0;其他需指定;
* return:成功返回新套接字的文件描述符,出错返回-1;
* */
1.2.2 bind
/**
* int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen)
* -------
* func:当使用socket创建套接字时,它存在于一个名称空间(地址族)中,但没有分配给它地址。 Bind()将addr
* 指定的地址分配给套接字;
* param sockfd:返回的文件描述符;
* param addr:传入sockaddr_in结构体且需要进行强制转换为(sockaddr);
serv_addr.sin_family = AF_INET; // 通信域
serv_addr.sin_port = htons(SERV_PORT); // 端口
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // IP
* param addrlen:传入addr的长度;
* return:成功返回0,失败返回-1;
* */
1.2.3 listen
/**
* int listen(int sockfd, int backlog)
* -------
* func:将套接字标记为被动套接字,即用于accept接收传入的连接请求的套接字;
* param sockfd:返回的文件描述符;
* param backlog:定义了sockfd挂起连接的队列可能增长的最大长度。 如果一个连接请求到达时队列已经满了,
* 客户端可能会收到一个指示ECONNREFUSED的错误,或者,如果底层协议支持重传,这个请求
* 可能会被忽略,这样之后的连接重试就会被放弃;
* return:成功返回0,失败返回-1;
* */
1.2.4 accept
/**
* int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
* -------
* func:侦听套接字sockfd的挂起连接队列中提取第一个连接请求,创建一个新的连接套接字,并返回一个指向该套
* 接字的新文件描述符;
* param sockfd:返回的文件描述符;
* param addr:指向sockaddr结构的指针,为传出参数;
* param addrlen:为传入传出参数;
* return:成功返回文件描述符,失败返回-1;
* */
1.2.5 ntohl()、htonl()、ntohs()、htons()
n代表net网络,h代表host主机,l代表long长整型,s代表short短整型
* ntohl():将一个无符号长整形数从网络字节顺序转换为主机字节顺序
* htonl():将一个无符号长整形数从主机字节顺序转换为网络字节顺序
* ntohs():将一个无符号短整形数从网络字节顺序转换为主机字节顺序
* htons():将一个无符号短整形数从主机字节顺序转换为网络字节顺序
1.2.6 connect
/**
* int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen)
* -------
* func:将套接字连接到由addr指定的地址;
* param sockfd:返回的文件描述符;
* param addr:指向sockaddr结构的指针;若sockfd的类型是SOCK_DGRAM,则addr是默认情况下发送数据报的地
址,也是接收数据报的唯一地址; 若为SOCK_STREAM或SOCK_SEQPACKET,此调用将尝试与绑定到
addr指定地址的套接字建立连接;
* param addrlen:传入结构体的大小;
* return:成功返回0,失败返回-1;
* */
1.2.7 inet_ntop
/**
* const char *inet_ntop(int af, const void *src,
char *dst, socklen_t size)
* -------
* func:将ip的网络字节序转换为字符串;
* param af:网络协议;
* param src:网络字节序的IP;
* param dst:接收字符串IP;
* param size:dst的大小;
* return:成功返回dst的非空指针,失败返回NULL;
* */
1.3 简易服务端程序
模拟客户端给服务器发送字符串,服务端将字母转换成大写并返回给客户端;
client
/*----------------------------------------------------------------------
> File Name: clientBase.cpp
> Author: Jxiepc
> Mail: Jxiepc
> Created Time: Sat 19 Feb 2022 11:55:25 AM CST
----------------------------------------------------------------------*/
#include <iostream>
#include <unistd.h>
#include <cstring>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define SERV_IP "127.0.0.1"
#define SERV_PORT 6666
void client() {
int cfd;
struct sockaddr_in serv_addr;
socklen_t ser_addr_len;
cfd = socket(AF_INET, SOCK_STREAM, 0);
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERV_PORT);
inet_pton(AF_INET, SERV_IP, &serv_addr.sin_addr.s_addr);
connect(cfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
while(1) {
char buf[128];
fgets(buf, 128, stdin);
write(cfd, buf, strlen(buf));
int n = read(cfd, buf, strlen(buf));
std::cout << buf;
}
}
int main(int argc, char* argv[])
{
client();
return 0;
}
server
/*----------------------------------------------------------------------
> File Name: serverBase.cpp
> Author: Jxiepc
> Mail: Jxiepc
> Created Time: Wed 09 Feb 2022 10:26:07 AM CST
----------------------------------------------------------------------*/
#include <iostream>
#include <unistd.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <ctype.h>
#include <arpa/inet.h>
#define SERV_IP "127.0.0.1"
#define SERV_PORT 6666
void server() {
int lfd, cfd;
struct sockaddr_in serv_addr;
struct sockaddr_in clie_addr;
socklen_t clie_addr_len;
char buf[1024];
int i;
lfd = socket(AF_INET, SOCK_STREAM, 0); // 建立连接
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERV_PORT);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(lfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)); // 绑定
listen(lfd, 128); // 监听
clie_addr_len = sizeof(clie_addr);
cfd = accept(lfd, (struct sockaddr *)&clie_addr, &clie_addr_len); // 接收客户端
char clie_ip[128];
while(1) {
std::cout << inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, clie_ip, sizeof(clie_ip))
<< ":" << ntohs(clie_addr.sin_port) << std::endl;
int len = read(cfd, buf, 1024); // 读取客户端的数据
for(int i=0; i<len; ++i)
buf[i] = toupper(buf[i]);
write(cfd, buf, len); // 向客户端发送数据
}
close(lfd);
close(cfd);
}
int main(int argc, char* argv[])
{
server();
return 0;
}