网络字节序
TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节
#include <arpa/inet.h>
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位长整数,s表示16位短整数。
如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回,
如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。
判断当前主机大小端
short tmp = 0x1234;
tmp = htons(tmp);
if ((*(char *)&tmp) == 0x34) {
printf("It's little\n");
} else if ((*(char *)&tmp) == 0x12) {
printf("It's big\n");
}
转换网络字节序为点分十进制的ip
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
af 使用的协议
src 指向网络字节中要转换为字符串的IP地址的指针
dst 字符串缓冲区
size 缓冲区大小
返回值:若成功则为指向结构的指针,若出错则为NULL
将点分十进制ip转换成网络字节序
int inet_pton(int af, const char *src, void *dst);
af 使用的协议
src 指向"xxx.xxx.xxx.xxx"格式的字符串
dst 指向struct sockaddr_in.sin_addr.s_addr结构体的指针
返回值:若成功则为1,若输入不是有效的表达式则为0,若出错则为-1
端口复用
使用setsockopt()设置socket描述符的选项SO_REUSEADDR为1,
表示允许创建端口号相同但IP地址不同的多个socket描述符
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
socket函数
int socket(int domain, int type, int protocol);
domain:
AF_INET 这是大多数用来产生socket的协议,使用TCP或UDP来传输,用IPv4的地址
AF_INET6 与上面类似,不过是来用IPv6的地址
AF_UNIX 本地协议,使用在Unix和Linux系统上,一般都是当客户端和服务器在同一台及其上的时候使用
type:
SOCK_STREAM 这个协议是按照顺序的、可靠的、数据完整的基于字节流的连接。这是一个使用最多的socket类型,这个socket是使用TCP来进行传输。
SOCK_DGRAM 这个协议是无连接的、固定长度的传输调用。该协议是不可靠的,使用UDP来进行它的连接。
SOCK_SEQPACKET该协议是双线路的、可靠的连接,发送固定长度的数据包进行传输。必须把这个包完整的接受才能进行读取。
SOCK_RAW socket类型提供单一的网络访问,这个socket类型使用ICMP公共协议。(ping、traceroute使用该协议)
SOCK_RDM 这个类型是很少使用的,在大部分的操作系统上没有实现,它是提供给数据链路层使用,不保证数据包的顺序
protocol:
传0 表示使用默认协议。
返回值:
成功:返回指向新创建的socket的文件描述符,失败:返回-1,设置errno
bind函数
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd:
socket文件描述符
addr:
构造出IP地址加端口号
addrlen:
sizeof(addr)长度
返回值:
成功返回0,失败返回-1, 设置errno
struct sockaddr_in
{
short sin_family;/*Address family一般来说AF_INET(地址族)PF_INET(协议族)*/
unsigned short sin_port;/*Port number(必须要采用网络数据格式,普通数字可以用htons()函数转换成网络数据格式的数字)*/
struct in_addr sin_addr;/*IP address in network byte order(Internet address)*/
unsigned char sin_zero[8];/*Same size as struct sockaddr没有实际意义,只是为了 跟SOCKADDR结构在内存中对齐*/
};
struct in_addr {
in_addr_t s_addr;
};
listen函数
int listen(int sockfd, int backlog);
sockfd:
socket文件描述符
backlog:
排队建立3次握手队列和刚刚建立3次握手队列的链接数和
如果有大量的客户端发起连接而服务器来不及处理,尚未accept的客户端就处于连接等待状态,listen()声明sockfd处于监听状态,并且最多允许有backlog个客户端处于待连接状态,如果接收到更多的连接请求就忽略
int accept(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len);
sockdf:
socket文件描述符
addr:
传出参数,返回链接客户端地址信息,含IP地址和端口号
addrlen:
传入传出参数(值-结果),传入sizeof(addr)大小,函数返回时返回真正接收到地址结构体的大小
返回值:
成功返回一个新的socket文件描述符,用于和客户端通信,失败返回-1,设置errno
connect函数
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockdf:
socket文件描述符
addr:
传入参数,指定服务器端地址信息,含IP地址和端口号
addrlen:
传入参数,传入sizeof(addr)大小
返回值:
成功返回0,失败返回-1,设置errno
shutdown函数
#include <sys/socket.h>
int shutdown(int sockfd, int how);
sockfd: 需要关闭的socket的描述符
how: 允许为shutdown操作选择以下几种方式:
SHUT_RD(0): 关闭sockfd上的读功能,此选项将不允许sockfd进行读操作。
该套接字不再接受数据,任何当前在套接字接受缓冲区的数据将被无声的丢弃掉。
SHUT_WR(1): 关闭sockfd的写功能,此选项将不允许sockfd进行写操作。进程不能在对此套接字发出写操作。
SHUT_RDWR(2): 关闭sockfd的读写功能。相当于调用shutdown两次:首先是以SHUT_RD,然后以SHUT_WR。
使用close中止一个连接,但它只是减少描述符的引用计数,并不直接关闭连接,只有当描述符的引用计数为0时才关闭连接。
shutdown不考虑描述符的引用计数,直接关闭描述符。也可选择中止一个方向的连接,只中止读或只中止写。
注意:
1. 如果有多个进程共享一个套接字,close每被调用一次,计数减1,直到计数为0时,也就是所用进程都调用了close,套接字将被释放。
2. 在多进程中如果一个进程调用了shutdown(sfd, SHUT_RDWR)后,其它的进程将无法进行通信。但,如果一个进程close(sfd)将不会影响到其它进程。
ssize_t send(int socket, const void *buffer, size_t length, int flags);
ssize_t sendmsg(int socket, const struct msghdr *message, int flags);
ssize_t sendto(int socket, const void *buffer, size_t length, int flags,
const struct sockaddr *dest_addr, socklen_t dest_len);
成功返回发送的字节数,失败返回0
ssize_t recv(int socket, void *buffer, size_t length, int flags);
ssize_t recvfrom(int socket, void *restrict buffer, size_t length, int flags,
struct sockaddr *restrict address, socklen_t *restrict address_len);
ssize_t recvmsg(int socket, struct msghdr *message, int flags);
server端和client端简单通信模型
server.c
1. socket() 建立套接字
2. bind() 绑定IP 端口号 (struct sockaddr_in addr 初始化)
3. listen() 指定最大同时发起连接数
4. accept() 阻塞等待客户端发起连接
5. read()
6. 小--大
7. write 给 客户端
8. close();
======================
client.c
1. socket();
2. bind(); 可以依赖“隐式绑定”
3. connect();发起连接
4. write();
5. read();
6. close();
server.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <ctype.h>
#define IP "127.0.0.1"
#define PORT 8888
int main() {
char ip_buf[1024];
struct sockaddr_in ser_addr, cli_addr;
int cfd, client_addr_len;
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1)
{
perror("server socket:");
return -1;
}
ser_addr.sin_family = AF_INET;
inet_pton(AF_INET, IP, &ser_addr.sin_addr.s_addr);
ser_addr.sin_port = htons(PORT);
bind(sockfd, (struct sockaddr *)&ser_addr, sizeof(ser_addr));
listen(sockfd, 1024); //设置服务端允许的最大监听个数
client_addr_len = sizeof(cli_addr);
// cli_addr是一个传出参数,里面包含了客户端的连接信息
cfd = accept(sockfd, (struct sockaddr *)&cli_addr, &client_addr_len); // 监听客户端连接,会阻塞
// 下面打印客户端ip 要在linux下验证,mac下多一个参数
//printf("client ip = %s clientport = ", inet_net_ntop(AF_INET, &cli_addr.sin_addr.s_addr, ip_buf, sizeof(ip_buf)));
char buf[1024];
int readCount;
while (1)
{
readCount = read(cfd, buf, sizeof(buf));
for (size_t i = 0; i < readCount; i++)
{
buf[i] = toupper(buf[i]);
}
write(cfd, buf, readCount);
}
close(cfd);
close(sockfd);
}
client.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <ctype.h>
#define IP "127.0.0.1"
#define PORT 8888
int main() {
struct sockaddr_in ser_addr;
// 创建一个socket,指定使用IPV4 TCP协议
int sfd = socket(AF_INET, SOCK_STREAM, 0);
if (sfd == -1)
{
perror("server socket:");
return -1;
}
bzero(ser_addr, sizeof(ser_addr)); // 初始化并清零一个地址结构
ser_addr.sin_family = AF_INET; //指定IPV4协议
inet_pton(AF_INET, IP, &ser_addr.sin_addr.s_addr); //指定IP,字符串类型转换为网络字节序
ser_addr.sin_port = htons(PORT); //指定端口
// 连接到指定的服务器进程
connect(sfd, (struct sockaddr *)&ser_addr, sizeof(ser_addr));
char buf[1024];
int count;
while(1) {
// 获取用户输入
fgets(buf, sizeof(buf), stdin);
// 将数据传递给服务器
write(sfd, buf, strlen(buf));
// 获取服务器返回的数据
count = read(sfd, buf, sizeof(buf));
// 写入到终端
write(STDOUT_FILENO, buf, count);
}
close(sfd);
}
网友评论