参考引用
Socket 本身有 “插座” 的意思,在 Linux 环境下,用于表示进程间网络通信的特殊文件类型
既然是文件,那么可以使用文件描述符引用套接字
在 TCP/IP 协议中,“IP 地址 + TCP 或 UDP 端口号” 唯一标识网络通信中的一个进程
套接字通信原理如下图所示
内存中的多字节数据相对于内存地址有大端和小端之分,磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分。网络数据流同样有大端小端之分,那么如何定义网络数据流的地址呢?
TCP/IP 协议规定,网络数据流应采用大端字节序,即低地址高字节,而发送主机可能是小端字节序,也可能是大端字节序。为使网络程序具有可移植性,使同样的 C 代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换(PC 机采用小端法,网络采用大端法)
#include <arpa/inet.h>
// h 表示 host,n 表示 network,l 表示 32 位长整数,s 表示 16 位短整数
uint32_t htonl(uint32_t hostlong); // 本地转网络(IP)
uint16_t htons(uint16_t hostshort); // 本地转网络(port)
uint32_t ntohl(uint32_t netlong); // 网络转本地(IP)
uint16_t ntohs(uint16_t netshort); // 网络转本地(port)
#include <arpa/inet.h>
// 本地字节序 (string 形式的 IP)---> 网络字节序(二进制形式的 IP)
// af:AF_INET(ipv4)、AF_INET6(ipv6)
// src:传入参数,本地字节序 IP 地址(点分十进制)
// dst:传出参数,转换后的网络字节序的 IP 地址
// 返回值:成功 1;异常 0,说明 src 指向的不是一个有效的 IP 地址;失败 -1
int inet_pton(int af, const char *src, void *dst);
// 网络字节序(二进制形式的 IP)---> 本地字节序 (string 形式的 IP)
// af:AF_INET(ipv4)、AF_INET6(ipv6)
// src:传入参数,网络字节序 IP 地址
// dst:传出参数,转换后的本地字节序(string IP)
// size:dst 的大小
// 返回值:成功,返回 dst;失败,返回 NULL
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
// inet_pton 和 inet_ntop 不仅可以转换 IPv4 的 in_addr,还可以转换 IPv6 的 in6_addr
// man 7 ip 命令查看下列定义
// strcut sockaddr 已过时,现在统一使用 struct sockaddr_in
struct sockaddr_in {
__kernel_sa_family_t sin_family; /* Address family 地址结构类型 */
__be16 sin_port; /* Port number 端口号 */
struct in_addr sin_addr; /* Internet address IP 地址 */
/* Pad to size of `struct sockaddr'. */
unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) -
sizeof(unsigned short int) - sizeof(struct in_addr)];
};
struct sockaddr_in addr;
addr.sin_family = AF_INET/AF_INET6/AF_UNIX; // AF_UNIX 用于本地套接字
addr.sin_port = htons(9527);
int dst;
inet_pton(AF_INET, "192.157.22.45", (void*)&dst);
addr.sin_addr.s_addr = dst;
addr.sin_addr.s_addr = htonl(INADDR_ANY); // 取出系统中有效的任意 IP 地址,二进制类型
bind(fd, (struct sockaddr *)&addr, size);
// 创建一个套接字
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
socket() 打开一个网络通讯端口,如果成功的话,就像 open() 一样返回一个文件描述符,应用程序可以像读写文件一样用 read/write 在网络上收发数据,如果 socket() 调用出错则返回 -1
服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接,因此服务器需要调用 bind 绑定一个固定的网络地址(IP)和端口号(port)到套接字
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd
addr
/*
struct sockaddr_in {
sa_family_t sin_family; // address family: AF_INET
in_port_t sin_port; // port in network byte order
struct in_addr sin_addr; // internet address
};
// internet address
struct in_addr {
uint32_t s_addr; // address in network byte order
};
*/
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8888);
// 因为服务器可能有多个网卡,每个网卡也可能绑定多个 IP 地址,这样设置可以在所有的 IP 地址上监听
// 直到与某个客户端建立了连接时才确定下来到底用哪个 IP 地址
addr.sin_addr.s_addr = htonl(INADDR_ANY); // INADDR_ANY 宏:表示取出系统中有效的任意 IP 地址,二进制类
(struct sockaddr *)&addr // addr 类型为 struct sockaddr*,故此处需要强制转换
addrlen
返回值
listen 函数用于设置同时与服务器建立连接的(监听)上限数 (同时进行 3 次握手的客户端数量)
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);
sockfd
backlog
典型的服务器程序可以同时服务于多个客户端
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
如果不使用 bind 绑定客户端地址结构,则系统默认采用 “隐式绑定”
#include <stdio.h>
#include <ctype.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#define SERV_PORT 9527
void sys_err(const char *str) {
perror(str);
exit(1);
}
int main(int argc, char *argv[]) {
int lfd = 0, cfd = 0;
int ret, i;
char buf[BUFSIZ], client_IP[1024];
struct sockaddr_in serv_addr, clit_addr; // 定义 服务器地址结构 和 客户端地址结构
socklen_t clit_addr_len; // 客户端地址结构大小
/*
struct sockaddr_in {
sa_family_t sin_family; // address family: AF_INET
in_port_t sin_port; // port in network byte order
struct in_addr sin_addr; // internet address
};
// internet address
struct in_addr {
uint32_t s_addr; // address in network byte order
};
*/
serv_addr.sin_family = AF_INET; // IPv4
serv_addr.sin_port = htons(SERV_PORT); // 转为网络字节序的 端口号
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 获取本机任意有效 IP
lfd = socket(AF_INET, SOCK_STREAM, 0); // 创建一个 socket
if (lfd == -1) {
sys_err("socket error");
}
bind(lfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)); // 给服务器 socket 绑定地址结构(IP+port)
listen(lfd, 128); // 设置监听上限
clit_addr_len = sizeof(clit_addr); // clit_addr_len为传入传出参数,此处获取客户端地址结构大小然后传入 accept
cfd = accept(lfd, (struct sockaddr *)&clit_addr, &clit_addr_len); // 阻塞等待客户端连接请求
if (cfd == -1)
sys_err("accept error");
printf("client ip:%s port:%d\n",
inet_ntop(AF_INET, &clit_addr.sin_addr.s_addr, client_IP, sizeof(client_IP)),
ntohs(clit_addr.sin_port)); // 根据 accept 传出参数,获取客户端 ip 和 port
while (1) {
ret = read(cfd, buf, sizeof(buf)); // 读客户端数据
write(STDOUT_FILENO, buf, ret); // 写到屏幕查看
for (i = 0; i < ret; i++) // 小写 -- 大写
buf[i] = toupper(buf[i]);
write(cfd, buf, ret); // 将大写,写回给客户端
}
close(lfd);
close(cfd);
return 0;
}
#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#define SERV_PORT 9527
void sys_err(const char *str) {
perror(str);
exit(1);
}
int main(int argc, char *argv[]) {
int cfd;
int conter = 10;
char buf[BUFSIZ];
struct sockaddr_in serv_addr; // 服务器地址结构
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERV_PORT);
inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr.s_addr);
cfd = socket(AF_INET, SOCK_STREAM, 0);
if (cfd == -1)
sys_err("socket error");
int ret = connect(cfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
if (ret != 0)
sys_err("connect err");
while (--conter) {
write(cfd, "hello\n", 6);
ret = read(cfd, buf, sizeof(buf));
write(STDOUT_FILENO, buf, ret);
sleep(1);
}
close(cfd);
return 0;
}
# 测试例程
$ gcc server.c -o server
$ ./server
client ip : 127.0.0.1 port : 46914
hello
hello
hello
...
# 另开一个终端
$ gcc client.c -o client
$ ./client
HELLO
HELLO
HELLO
...
#include "wrap.h"
void perr_exit(const char *s) {
perror(s);
exit(-1);
}
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr) {
int n;
again:
if ((n = accept(fd, sa, salenptr)) < 0) {
if ((errno == ECONNABORTED) || (errno == EINTR))
goto again;
else
perr_exit("accept error");
}
return n;
}
int Bind(int fd, const struct sockaddr *sa, socklen_t salen) {
int n;
if ((n = bind(fd, sa, salen)) < 0)
perr_exit("bind error");
return n;
}
int Connect(int fd, const struct sockaddr *sa, socklen_t salen) {
int n;
n = connect(fd, sa, salen);
if (n < 0) {
perr_exit("connect error");
}
return n;
}
int Listen(int fd, int backlog) {
int n;
if ((n = listen(fd, backlog)) < 0)
perr_exit("listen error");
return n;
}
int Socket(int family, int type, int protocol) {
int n;
if ((n = socket(family, type, protocol)) < 0)
perr_exit("socket error");
return n;
}
ssize_t Read(int fd, void *ptr, size_t nbytes) {
ssize_t n;
again:
if ( (n = read(fd, ptr, nbytes)) == -1) {
if (errno == EINTR)
goto again;
else
return -1;
}
return n;
}
ssize_t Write(int fd, const void *ptr, size_t nbytes) {
ssize_t n;
again:
if ((n = write(fd, ptr, nbytes)) == -1) {
if (errno == EINTR)
goto again;
else
return -1;
}
return n;
}
int Close(int fd) {
int n;
if ((n = close(fd)) == -1)
perr_exit("close error");
return n;
}
/*参三: 应该读取的字节数*/ //socket 4096 readn(cfd, buf, 4096) nleft = 4096-1500
ssize_t Readn(int fd, void *vptr, size_t n) {
size_t nleft; //usigned int 剩余未读取的字节数
ssize_t nread; //int 实际读到的字节数
char *ptr;
ptr = vptr;
nleft = n; //n 未读取字节数
while (nleft > 0) {
if ((nread = read(fd, ptr, nleft)) < 0) {
if (errno == EINTR)
nread = 0;
else
return -1;
} else if (nread == 0)
break;
nleft -= nread; //nleft = nleft - nread
ptr += nread;
}
return n - nleft;
}
ssize_t Writen(int fd, const void *vptr, size_t n) {
size_t nleft;
ssize_t nwritten;
const char *ptr;
ptr = vptr;
nleft = n;
while (nleft > 0) {
if ( (nwritten = write(fd, ptr, nleft)) <= 0) {
if (nwritten < 0 && errno == EINTR)
nwritten = 0;
else
return -1;
}
nleft -= nwritten;
ptr += nwritten;
}
return n;
}
static ssize_t my_read(int fd, char *ptr) {
static int read_cnt;
static char *read_ptr;
static char read_buf[100];
if (read_cnt <= 0) {
again:
if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) { //"hello\n"
if (errno == EINTR)
goto again;
return -1;
} else if (read_cnt == 0)
return 0;
read_ptr = read_buf;
}
read_cnt--;
*ptr = *read_ptr++;
return 1;
}
/*readline --- fgets*/
//传出参数 vptr
ssize_t Readline(int fd, void *vptr, size_t maxlen) {
ssize_t n, rc;
char c, *ptr;
ptr = vptr;
for (n = 1; n < maxlen; n++) {
if ((rc = my_read(fd, &c)) == 1) { //ptr[] = hello\n
*ptr++ = c;
if (c == '\n')
break;
} else if (rc == 0) {
*ptr = 0;
return n-1;
} else
return -1;
}
*ptr = 0;
return n;
}
#ifndef __WRAP_H_
#define __WRAP_H_
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string>
#include <ctype.h>
void perr_exit(const char *s);
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr);
int Bind(int fd, const struct sockaddr *sa, socklen_t salen);
int Connect(int fd, const struct sockaddr *sa, socklen_t salen);
int Listen(int fd, int backlog);
int Socket(int family, int type, int protocol);
ssize_t Read(int fd, void *ptr, size_t nbytes);
ssize_t Write(int fd, const void *ptr, size_t nbytes);
int Close(int fd);
ssize_t Readn(int fd, void *vptr, size_t n);
ssize_t Writen(int fd, const void *vptr, size_t n);
ssize_t my_read(int fd, char *ptr);
ssize_t Readline(int fd, void *vptr, size_t maxlen);
#endif