一、概念
IP地址:在网络环境中唯一标识一台主机。
端口号(port):在主机中唯一标识一个进程。
socket:就是所谓的套接字,在Linux中socket是一种伪文件(不占用内存),用一个文件描述符指代。
网络字节序:
大端序:低地址存高位,TCP/IP协议规定网络数据流应采用大端字节序。
小端序:低地址存低位,计算中存储数据常用的格式。
二、常用函数
字节序转换函数:
uint32_t htonl(uint32_t hostlong)
uint16_t htons(uint16_t hostshort)
uint32_t ntohl(uint32_t netlong)
uint16_t ntohs(uint16_t netshort)
h代表host(主机),n代表network,l表示32位长整数(IP地址),s表示16位短整数(端口号)
IP地址转换:
点分十进制字符串 ——> uint32_t ——> htonl() ——> 网络字节序
点分十进制字符串 ——> inet_pton() ——> 网络字节序
网络字节序 ——> inet_ntop() ——> 点分十进制字符串
网络套接字函数
创建套接字,成功返回套接字描述符,失败返回-1
int socket (int __domain, int __type, int __protocol)
绑定套接字
int bind (int __fd, const struct sockaddr *, socklen_t __len)
开始接收套接字上的连接,成功返回0,失败返回-1,并设置errno
int listen (int __fd, int __n)
接受客户端连接请求,如果没有则阻塞等待,成功返回0,失败返回-1,并设置errno
int accept (int __fd, struct sockaddr *__restrict, socklen_t *__restrict __addr_len)
向对端发起连接请求,成功返回0,失败返回-1,并设置errno
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
对套接字的引用计数减1,当套接字的引用计数为0时,关闭套接字,并回收释放套接字所占资源
int close (int __fd)
不管套接字的引用计数为多少,直接关闭套接字的读端或者写端或者全部,但是并不回收释放套接字所占用资源
int shutdown(int sockfd, int how)
how参数可为:SHUT_RD、SHUT_WR、SHUT_RDWR
将套接字设置为非阻塞
fcntl(fd, F_SETFL, O_NONBLOCK)
设置可重用套接字所绑定的地址以及端口号
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &op, sizeof(op))
读写函数
ssize_t read(int fd, void *buf, size_t nbytes)
ssize_t write(int fd, const void *buf, size_t count)
ssize_t recv (int __fd, void *__buf, size_t __n, int __flags)
ssize_t send (int __fd, __const void *__buf, size_t __n, int __flags)
三、socket模型创建流程图
四、编程例子(为了突出逻辑性,所以没有返回值检查)
服务器:
#include<iostream>
#include<unistd.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<cctype>
#define SERV_IP "192.168.200.131" //服务器IP
#define SERV_PORT 6666 //服务器端口
int main() {
//-------创建套接字-------
int sfd = socket(AF_INET, SOCK_STREAM, 0);
//-------绑定套接字-------
sockaddr_in servaddr; //套接字信息存储
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT); //端口值转换成网络字节序
inet_pton(AF_INET, SERV_IP, &servaddr.sin_addr); //IP地址转换成网络字节序
bind(sfd, (sockaddr *)&servaddr, sizeof(servaddr)); //绑定套接字
//-------设置最大待连接请求-------
listen(sfd, 128);
//-------阻塞等待连接,当有连接请求时接收连接--------
sockaddr_in clieaddr;
socklen_t clieaddr_struct_size;
int cfd = accept(sfd, (sockaddr *)&clieaddr, &clieaddr_struct_size);
//-------处理客户端请求-------
char buf[4096];
ssize_t readlen = 0; //从客户端读到的数据的长度
while (1) {
readlen = read(cfd, buf, sizeof(buf));
if (readlen == 0) break;
//执行客户端请求——小写转大写
for (int i = 0; i < readlen; ++i)
buf[i] = toupper(buf[i]);
write(cfd, buf, readlen); //将请求结果写回客户端
}
close(cfd);
close(sfd);
return 0;
}
客户端
#include<iostream>
#include<unistd.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<cstring>
#include<cstdio>
#define SERV_IP "192.168.200.131"
#define SERV_PORT 6666
int main() {
//-------创建套接字--------
int cfd = socket(AF_INET, SOCK_STREAM, 0);
//-------绑定套接字(客户端可以不执行该操作,因为操作系统会隐式绑定)--------
//-------请求连接服务器--------
sockaddr_in servaddr; //编写服务器的IP地址与端口号
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
inet_pton(AF_INET, SERV_IP, &servaddr.sin_addr);
connect(cfd, (sockaddr *)&servaddr, sizeof(servaddr));
//-------请求服务器---------
char buf[4096];
ssize_t readlen = 0; //服务器回写的内容长度
ssize_t writelen = 0; //写给服务器的数据长度
while (1) {
std::cin.getline(buf, sizeof(buf));
writelen = write(cfd, buf, strlen(buf));
if (writelen == 0) break;
readlen = read(cfd, buf, sizeof(buf));
for (int i = 0; i < readlen; ++i)
std::cout << buf[i];
std::cout << std::endl;
}
close(cfd);
return 0;
}