转码请带上原文链接:Linux socket api
https://www.daemoncoder.com/a/Linux%20socket%20api/4d54633d
创建socket
linux在 sys/socket.h 下定义了 socket() 系统调用,来创建一个socket,返回一个文件描述符,读写文件的函数也可以用来操作socket。
#include <sys/socket.h>
int socket(int domain, int type, int protocal);
- domain参数
在之前 Linux socket地址结构体 文章中介绍了三种协议族,domain参数用来指定用哪种协议族,常见的是 PF_INET(IPv4)、PF_INET6(IPv6) 和 PF_UNIX(Unix本地域)。
- type参数
type参数指定服务类型,常见的是SOCK_STREAM(流服务)、SOCK_DGRAM(数据报)、SOCK_RAW(原始套接字)。对于TCP/IP协议族,需要指定type为 SOCK_STREAM,如果想使用UDP协议则用 SOCK_DGRAM。SOCK_RAW 除了可以处理普通的网络报文之外,可以用来处理前面所述无法处理的报文,如ICMP、IGMP等,可以由用户构造IP头,总体来说,SOCK_RAW 可以处理一些特殊协议报文以及操作IP层及其以上的数据。
- protocal参数
protocal参数表示在前面两个参数筛选出的协议集合中,再指定一个具体的协议,传入0表示使用默认协议。通常前两个参数就可以确定一个要使用的协议了,一般这个参数传0就可以。
- 返回
socket()返回一个文件描述符,对socket操作的其他函数,往往需要传入这个值来指定要操作的是哪个socket。socket()调用失败返回-1,并设置errno。用户可以 #include <errno.h>,然后使用errno变量。
命名socket
通常,网络编程中服务器端程序要绑定一个socket地址,如tcp上的80端口,来提供给客户端连接。给socket绑定地址叫做命名socket,不命名socket表示采用操作系统自动分配的地址。服务器端通常需要命名socket,客户端一般不需要。
linux提供的命名socket的系统调用为:
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- sockfd参数
前面所述socket()返回的文件描述符。
- addr参数
指定要绑定的socket地址,sockaddr类型的指针,可以由sockaddr_in、sockaddr_in6、sockaddr_un等类型强转过来,详见 Linux socket地址结构体。
- addrlen参数
上述addr参数所指结构的大小。
- 返回
成功时返回0,失败返回-1并设置errno。bind()常见的errno值为 EACCES 和 EADDRINUSE。EACCES表示当前用户没有权限绑定的地址,如端口0~1023。EADDRINUSE表示当前地址已经被占用。
监听socket
命名socket之后,还不能马上接收连接,需要创建一个队列,以便客户端连接较多时,存储待处理的连接。如果队列被占满,后续再在客户端连接,会收到 ECONNREFUSED 错误,即连接被拒绝。
linux提供了listen系统调来创建这个队列:
#include <sys/socket.h>
int listen(int sockfd, int backlog);
- sockfd参数
前面所述socket()返回的文件描述符。
- backlog参数
指定队列的大小。
- 返回
成功时返回0,失败返回-1并设置errno。
接受连接
linux提供了accept系统调用来接受连接:
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
- sockfd参数
前面所述socket()返回的文件描述符。
- addr参数
接受客户端连接的socket地址。
- addrlen参数
上述addr参数所指结构的大小。
- 返回
失败返回-1并设置errno。成功时,返回一个新的文件描述符,对应接受的客户端连接,后续与客户端通信时读写此文件描述符即可。
读写数据
文件读写的函数同样可以用来读写socket,但是系统还提供了专门用于socket数据读写的函数:
#include <sys/socket.h>
// 用于处理TCP的函数
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
// 用于处理UDP的函数
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
发起连接
connect函数用于客户端发起连接。
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);
成功返回0,失败返回-1并设置errno。常见的errno为 ECONNREFUSED 和 ETIMEDOUT,分别表示服务端拒绝连接、连接超时。
关闭连接
关闭文件的函数可以用来关闭socket连接,如close()函数:
#include <unistd.h>
int close(int sockfd);
当在多进程编程中,一个进程fork之后得到的父子进程都可以操作原来的socket,这种情况下fork时会把原来父进程中打开的socket的引用记数加1,无论是在父进程还是子进程中,调用close()时只是把socket的引用记数减1,减到0时才会真正地关闭连接。如果想要立即关闭连接,而不是减引用记数的话,可以用专门针对网络编程的shutdown()函数:
#include <sys/socket.h>
int shutdown(int sockfd, int howto);
howto参数提供了三种关闭方式:
- SHUT_RD:关闭sockfd上的读,应用程序不能再执行读操作,并且socket接收缓冲区中的数据都被丢弃。
- SHUT_WR:关闭写,应用程序不能再执行写操作,但是发送缓冲区中的数据会在真正关闭连接之前全部发送出去。
- SHUT_RDWR:同时关闭读写。
shutdown()成功时返回0,失败返回-1并设置errno。
服务器端示例
#include <stdio.h>
#include <assert.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
void demo() {
// www.daemoncoder.com
const char *ip = "127.0.0.1";
int port = 10086;
int sockfd = socket(PF_INET, SOCK_STREAM, 0);
assert(sockfd > 0);
struct sockaddr_in address;
bzero(&address, sizeof(address));
address.sin_family = AF_INET;
inet_pton(AF_INET, ip, &address.sin_addr);
address.sin_port = htons(port);
int ret = bind(sockfd, (struct sockaddr *)&address, sizeof(address));
if (-1 == ret) {
if (errno == EACCES) {
exit(1); // 没有权限
} else if (errno == EADDRINUSE) {
exit(2); // 端口被占用
} else {
exit(errno);
}
}
ret = listen(sockfd, 1024);
assert(0 == ret);
int i = 0;
while (i++ < 3) {
struct sockaddr_in client;
socklen_t client_addrlen = sizeof(client);
int connfd = accept(sockfd, (struct sockaddr*)&client, &client_addrlen);
if (connfd < 0) {
printf("accept failed, errno:%d\n", errno);
} else {
char client_ip[INET_ADDRSTRLEN];
printf("connected with %s:%d\n", inet_ntop(AF_INET, &client.sin_addr, client_ip, INET_ADDRSTRLEN), ntohs(client.sin_port));
char data[1024];
recv(connfd, data, 1024, 0);
printf("receive data:%s\n", data);
}
}
close(sockfd);
}
客户端端示例
#include <stdio.h>
#include <sys/socket.h>
#include <assert.h>
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
void demo() {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
assert(sockfd > 0);
char *server_ip = "127.0.0.1";
int server_port = 10086;
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
inet_pton(AF_INET, server_ip, &server_addr.sin_addr);
server_addr.sin_port = htons(server_port);
int ret = connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
if (-1 == ret) {
printf("connect failed, errno:%d\n", errno);
}
char data[] = "www.daemoncoder.com";
send(sockfd, data, strlen(data), 0);
close(sockfd);
}
本文原文链接:
https://www.daemoncoder.com/a/Linux%20socket%20api/4d54633d
更多文章推荐:
Linux socket地址结构体
https://www.daemoncoder.com/a/Linux%20socket%E5%9C%B0%E5%9D%80%E7%BB%93%E6%9E%84%E4%BD%93/4d54593d
MySQL分页offset过大性能问题与优化
https://www.daemoncoder.com/a/MySQL%E5%88%86%E9%A1%B5offset%E8%BF%87%E5%A4%A7%E6%80%A7%E8%83%BD%E9%97%AE%E9%A2%98%E4%B8%8E%E4%BC%98%E5%8C%96/4d513d3d
MySQL大小写不敏感问题
https://www.daemoncoder.com/a/MySQL%E5%A4%A7%E5%B0%8F%E5%86%99%E4%B8%8D%E6%95%8F%E6%84%9F%E9%97%AE%E9%A2%98/4e413d3d
————————————————
版权声明:本文为CSDN博主「DaemonCoder」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u011296355/article/details/102796673