获取地址
getpeername()
用于获取与某个套接字关联的对端地址,accept()
在接收连接的时候也会获取对端的地址,getsockname()
用于获取本地地址。
// Server
#include <stdio.h>
#include <unistd.h>
#include <arpa/inet.h>
int main()
{
int svrfd, clifd;
struct sockaddr_in addr;
svrfd = socket(AF_INET, SOCK_STREAM, 0);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr =htonl(INADDR_ANY);
addr.sin_port = htons(8888);
bind(svrfd, (const struct sockaddr *)&addr, sizeof(struct sockaddr_in));
listen(svrfd, 5);
struct sockaddr_in addrcli, peeraddr;
socklen_t len = sizeof(struct sockaddr_in);
clifd = accept(svrfd, (struct sockaddr *)&addrcli, &len);
printf("Client #%d ip=%s port=%d\n", clifd,
inet_ntoa(addrcli.sin_addr), ntohs(addrcli.sin_port));
len = sizeof(struct sockaddr_in);
getpeername(clifd, (struct sockaddr *)&peeraddr, &len);
printf("Peer #%d ip=%s port=%d\n", clifd,
inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
getchar();
close(svrfd);
close(clifd);
return 0;
}
// Client
#include <stdio.h>
#include <unistd.h>
#include <arpa/inet.h>
int main()
{
int rc;
int sockfd;
struct sockaddr_in addr;
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
addr.sin_family = AF_INET;
addr.sin_port = htons(8888);
printf("Server ip=%s port=%d\n",
inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
sockfd = socket(AF_INET, SOCK_STREAM, 0);
rc = connect(sockfd, (const struct sockaddr *)&addr, sizeof(struct sockaddr_in));
struct sockaddr_in svraddr;
socklen_t len = sizeof(struct sockaddr_in);
getsockname(sockfd,(struct sockaddr *)&svraddr, &len);
printf("Local #%d ip=%s port=%d\n", sockfd,
inet_ntoa(svraddr.sin_addr), ntohs(svraddr.sin_port));
getchar();
close(sockfd);
return 0;
}
直接编译运行,然后通过 netstat -atunp | grep 8888
查看。
Unix
如果使用的是 Unix Domain Socket 那么可以获取到对端的 PID UID GID 信息,可以用来判断对方是否有相应的权限。
客户端在发送的时候也可以指定用户,但是必须要有 CAP_SYS_ADMIN
权限才可以。
struct ucred cred;
socklen_t len;
len = sizeof(struct ucred);
getsockopt(client_fd, SOL_SOCKET, SO_PEERCRED, &cred, &len);
printf("Credentials from SO_PEERCRED: pid=%d, uid=%d, gid=%d\n", cred.pid, cred.uid, cred.gid);
非阻塞链接
Windows 平台上无论利用 socket()
函数还是 WSASocket()
创建的 socket 都是阻塞模式的,而 Linux 上可以在创建 Socket 时将其指定为异步,例如。
socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP);
如果已经创建了 Socket (实际上其它的描述符也是支持的),还可以通过以下 API 函数来设置。
Linux 平台上可以调用 fcntl()
或者 ioctl()
函数,实例如下:
fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFL, 0) | O_NONBLOCK);
ioctl(sockfd, FIONBIO, 1); /* 1: NONBLOCK, 0: BLOCK */
Accept
默认,Windows 和 Linux 上的 accept()
函数返回的 socekt 也是阻塞的,而 Linux 另外提供了一个 accept4()
函数,可以直接将返回的 socket 设置为非阻塞模式。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags);
只要将 accept4()
最后一个参数 flags 设置成 SOCK_NONBLOCK
即可。
Recv & Send
实际上在接收和发送数据的时候,Linux 中也就对应了 send()
sendto()
recv()
recvfrom()
之类的函数,可以在调用的时候将其中的 flags 设置为 MSG_DONTWAIT
即可。
建立链接
对于面向连接的 socket 类型,如 SOCK_STREAM,在通过 connect()
函数建立链接时,对于 TCP 需要三次握手过程。
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
正常返回 0 ,失败返回 -1 并设置 errno 标示失败的原因,常见原因是主机不可达或超时。
对于 TCP 套接字,在通过 connect() 函数建立链接时需要经过三次握手,Linux 内核默认的超时时间是 75s ,如果是阻塞模式,那么 connect() 会等到链接建立成功或者超时失败,这也就导致如果服务异常,每个客户端都要等待 75s 才可能退出。
使用非阻塞 connect 时需要注意的问题是:
- 很可能 调用 connect 时会立即建立连接(比如,客户端和服务端在同一台机子上),必须处理这种情况。
- POSIX 定义了两个与非阻塞相关的内容:
- 成功建立连接时,socket 描述字变为可写,报错可以通过 getsockopt() 获取。
- 连接建立失败时,socket 描述字既可读又可写同时报错,可以优先检查报错退出。
#include <sys/epoll.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/select.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int select_version(int sockfd)
{
fd_set rset, wset;
FD_ZERO(&rset);
FD_ZERO(&wset);
FD_SET(sockfd, &rset);
FD_SET(sockfd, &wset);
struct timeval tval;
tval.tv_sec = 0;
tval.tv_usec = 300 * 1000;
int rdynum;
rdynum = select(sockfd + 1, &rset, &wset, NULL, &tval);
if (rdynum < 0) {
fprintf(stderr, "Select error, %s\n", strerror(errno));
return -1;
} else if (rdynum == 0) {
fprintf(stderr, "Select timeout\n");
return -1;
}
if (FD_ISSET(sockfd, &rset)) {
fprintf(stderr, "read \n");
}
if (FD_ISSET(sockfd, &wset)) {
fprintf(stderr, "write \n");
}
return 0;
}
int epoll_version(int sockfd)
{
int ep, i, evtnum;
ep = epoll_create(1024); /* ignore 1024 since Linux 2.6.8 */
if (ep < 0) {
fprintf(stderr, "Create epoll error, %s\n", strerror(errno));
return -1;
}
struct epoll_event event;
event.events = EPOLLIN | EPOLLOUT | EPOLLET;
event.data.fd = sockfd;
epoll_ctl(ep, EPOLL_CTL_ADD, sockfd, &event);
int err = 0;
int errlen = sizeof(err);
struct epoll_event events[64];
evtnum = epoll_wait(ep, events, sizeof(events), 60 * 1000);
for (i = 0; i < evtnum; i++) {
int fd = events[i].data.fd;
if (events[i].events & EPOLLERR) {
fprintf(stderr, "error\n");
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) {
fprintf(stderr, "getsockopt(SO_ERROR): %s", strerror(errno));
close(fd);
//return -1;
}
if (err) {
fprintf(stderr, "%s\n", strerror(err));
close(fd);
//return -1;
}
}
if (events[i].events & EPOLLIN) {
fprintf(stderr, "in\n");
}
if (events[i].events & EPOLLOUT) {
fprintf(stderr, "out\n");
}
}
return 0;
}
int main(int argc, char** argv)
{
struct sockaddr_in svraddr;
int sockfd, rc;
memset(&svraddr, 0, sizeof(svraddr));
svraddr.sin_family = AF_INET;
svraddr.sin_port = htons(8009);
inet_aton("127.0.0.1", &svraddr.sin_addr.s_addr);
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("Create socket fail");
return -1;
}
if (fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFL, 0) | O_NONBLOCK) < 0) {
perror("Set socket O_NONBLOCK fail");
return -1;
}
rc = connect(sockfd, (struct sockaddr *)&svraddr, sizeof(struct sockaddr_in));
if (rc < 0 && errno != EINPROGRESS) {
perror("Connect remote server fail");
return -1;
}
//select_version(&c_fd);
epoll_version(sockfd);
close(sockfd);
return 0;
}