相关函数释义
Socket
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
/* 功能:创建套接字
domain:协议族
type:套接字类型
protocol:协议类型
返回值:成功返回套接字描述符号,出错的时候返回-1
*/
domain现在能使用的类型如下,现在常用的类型是AF_INET。
AF_UNIX, AF_LOCAL /* Local communication unix(7) */
AF_INET /* IPv4 Internet protocols ip(7) */
AF_INET6 /* IPv6 Internet protocols ipv6(7) */
AF_IPX /* IPX - Novell protocols */
AF_NETLINK /* Kernel user interface device netlink(7) */
AF_X25 /* ITU-T X.25 / ISO-8208 protocol x25(7) */
AF_AX25 /* Amateur radio AX.25 protocol */
AF_ATMPVC /* Access to raw ATM PVCs */
AF_APPLETALK /* AppleTalk ddp(7) */
AF_PACKET /* Low level packet interface packet(7) */
AF_ALG /* Interface to kernel crypto API */
type套接字类型有定义如下:
SOCK_STREAM /* Provides sequenced, reliable, two-way, connection-based byte streams. An out-of-band data transmission mechanism may be supported. */
SOCK_DGRAM /* Supports datagrams (connectionless, unreliable messages of a fixed maximum length). */
SOCK_SEQPACKET /* Provides a sequenced, reliable, two-way connection-based data transmission path for datagrams of fixed maximum length; a consumer is required to read an entire packet with each input system call. */
SOCK_RAW /* Provides raw network protocol access. */
SOCK_RDM /* Provides a reliable datagram layer that does not guarantee ordering. */
SOCK_PACKET /* Obsolete and should not be used in new programs; see packet(7). */
protocol协议类型有定义如下:
IPPROTO_IP /* IP传输协议 */
IPPROTO_TCP /* TCP传输协议 */
IPPROTO_UDP /* UDP传输协议 */
IPPROTO_STCP /* STCP传输协议 */
IPPROTO_TIPC /* TIPC传输协议 */
bind
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
/* 功能:将本地协议地址与sockfd绑定
sockfd:套接字描述符
addr:一个指向sockaddr结构体类型的指针
addrlen:sockaddr结构的长度
返回值:成功返回0,失败返回-1
*/
通用的网络地址标识为sockaddr,为了使不同格式的地址能够传入到套接字函数,地址需要被强制转换成sockaddr类型。struct sockaddr的定义如下:
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
}
Internet的地址定义在<netinet/in.h>,在IPV4因特网中(AF_INET)中,套接字的地址结构如下:
struct sockaddr_in {
sa_family_t sin_family; /*address family*/
in_port_t sin_port; /*port*/
struct in_addr sin_addr; /*address*/
};
struct in_addr {
in_addr_t s_addr;
}
sockaddr和sockaddr_in二者长度一样,都是16个字节,即占用的内存大小是一致的,因此可以互相转化。二者是并列结构,指向sockaddr_in结构的指针也可以指向sockaddr。
listen
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);
/* 功能:监听本地端口
sockfd:套接字描述符
backlog:连接请求队列的最大长度(一般由2到4)
返回值:成功返回0,失败返回-1
*/
accept
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
/* 功能:建立连接
sockfd:套接字描述符
addr:一个指向sockaddr结构体类型的指针
addrlen:sockaddr结构的长度
返回值:成功返回0,失败返回-1
*/
connect
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
/* 功能:建立与指定socket的连接
sockfd:套接字描述符
addr:一个指向sockaddr结构体类型的指针
addrlen:sockaddr结构的长度
返回值:成功返回0,失败返回-1
*/
recvmsg/sendmsg
socket发送和接收的函数有如下几对:recv/send、readv/writev、recvfrom/sendto、recvmsg/sendmsg。
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
其他
计算机处理器的结构不同,字节序可能不同,分为大端可小端,大端模式下高地址存储低字节,小端模式相反。在不同的计算机之间通信,那么需要考虑字节序。网络协议指定了字节序称之为网络字节序,在利用socket进行通信时,应用程序可能需要在网络字节序与主机字节序之间进行转换,以确保交换格式化数据时字节序不会出现问题。TCP/IP使用大端字节序,在<arpa/inet.h>中定义了4个网络字节序转换函数如下:
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostint32); /*将32位的数据从主机字节序转换为网络字节序*/
uint16_t htons(uint16_t hostint16); /*将16位的数据从主机字节序转换为网络字节序*/
uint32_t ntohl(uint32_t netint32); /*将32位的数据从网络字节序转换为主机字节序*/
uint16_t ntohs(uint16_t netint16); /*将16位的数据从网络字节序转换为主机字节序*/
网络IP地址的转换函数如下:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int inet_aton(const char *cp, struct in_addr *inp);
in_addr_t inet_addr(const char *cp); /* 将字符串形式的IP地址转换为网络字节顺序的整型值 */
in_addr_t inet_network(const char *cp);
char *inet_ntoa(struct in_addr in); /* 网络字节顺序的整型值转换为字符串形式的IP地址 */
struct in_addr inet_makeaddr(in_addr_t net, in_addr_t host);
in_addr_t inet_lnaof(struct in_addr in);
in_addr_t inet_netof(struct in_addr in);
客户端程序
Client端步骤如下:
- 调用socket函数来创建一个IPV4(AF_INET)字节流(SOCK_STREAM)套接字,返回标识符
- 将服务器的IP地址和端口号填入sockaddr_t结构的变量中,地址族设定为AF_INET,IP地址和端口必须要是特地格式,使用htons转换端口,使用inet_pton转换IP地址
- 调用connet函数和指定的服务器建立一个TCP连接
- 使用read/write来与服务器通信
代码
客户端通过终端输入字串,发送到服务器端,然后从服务器端接收字串,输出到终端。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
static void do_msg_process(int socketfd) {
printf("do_msg_process enter!\n");
int size = 0;
char read_buffer[512] = {0};
char write_buffer[1024] = {0};
while(true) {
memset(read_buffer, 0x00, sizeof(read_buffer));
memset(write_buffer, 0x00, sizeof(write_buffer));
printf("input:> ");
fgets(write_buffer, 1024, stdin);
size = write(socketfd, write_buffer, strlen(write_buffer));
size = read(socketfd, read_buffer, 1024);
printf("server say: %s\n", read_buffer);
}
printf("do_msg_process exit!\n");
}
int main(int argc, char const *argv[]) {
// 利用socket函数来创建一个IPV4(AF_INET)字节流(SOCK_STREAM)套接字,返回标识符
int socketfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (socketfd < 0) {
perror("socket create failed!\n");
return 0;
}
// 将服务器的IP地址和端口号填入sockaddr_t结构的变量中,地址族设定为AF_INET,IP地址和端口必须要是特地格式,使用htons转换端口,使用inet_pton转换IP地址
struct sockaddr_in c_addr;
memset(&c_addr, 0x00, sizeof(struct sockaddr_in));
c_addr.sin_family = AF_INET;
c_addr.sin_port = htons(6666);
inet_pton(AF_INET, "127.0.0.1", &c_addr.sin_addr);
// connet将和指定的服务器建立一个TCP连接
int result = connect(socketfd, (struct sockaddr*)&c_addr, sizeof(struct sockaddr_in));
if (result < 0) {
perror("connect failed!\n");
return 0;
}
// 处理消息
do_msg_process(socketfd);
close(socketfd);
return 0;
}
服务端程序
Server端步骤如下:
- 调用socket函数来创建一个IPV4(AF_INET)字节流(SOCK_STREAM)套接字,返回标识符
- 将服务器的IP地址和端口号填入sockaddr_t结构的变量中,地址族设定为AF_INET,IP地址和端口必须要是特地格式,IP地址使用INADDR_ANY表示任意IP
- 调用bind函数绑定所创建的套接字
- 调用listen将套接字转换为监听套接字,这样外来连接可以通过该套接字被内核接受
- 调用accept,服务器进入睡眠状态,等待客户的连接,完成3次握手,然后返回一个握手成功的描述符,新描述符用于与客户端通讯
- 使用read/write与客户端通讯
- 调用close来关闭与客户端的连接
代码
服务器端接收客户端字串,然后将字串增加[SERVER]:
头,再返回给客户端。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
static void do_msg_process(int accsocfd) {
printf("do_msg_process enter!\n");
int size = 0;
char read_buffer[512] = {0};
char write_buffer[1024] = {0};
while (true) {
memset(read_buffer, 0x00, sizeof(read_buffer));
memset(write_buffer, 0x00, sizeof(write_buffer));
size = read(accsocfd, read_buffer, 1024);
printf("client say: %s\n", read_buffer);
snprintf(write_buffer, 1024, "%s%s", "[SERVER]: ", read_buffer);
size = write(accsocfd, write_buffer, strlen(write_buffer));
}
printf("do_msg_process exit!\n");
}
int main(int argc, char const *argv[]) {
// 调用socket函数来创建一个IPV4(AF_INET)字节流(SOCK_STREAM)套接字,返回标识符
int socketfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (socketfd < 0) {
perror("socket create failed!\n");
return 0;
}
// 将服务器的IP地址和端口号填入sockaddr_t结构的变量中,地址族设定为AF_INET,IP地址和端口必须要是特地格式,IP地址使用INADDR_ANY表示任意IP
struct sockaddr_in s_addr;
memset(&s_addr, 0x00, sizeof(struct sockaddr_in));
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(6666);
s_addr.sin_addr.s_addr = htonl(INADDR_ANY);
// 调用bind函数绑定所创建的套接字
bind(socketfd, (struct sockaddr*)&s_addr, sizeof(struct sockaddr));
// 调用listen将套接字转换为监听套接字,这样外来连接可以通过该套接字被内核接受
listen(socketfd, 10);
while (true) {
// 调用accept,服务器进入睡眠状态,等待客户的连接,完成3次握手,然后返回一个握手成功的描述符,新描述符用于与客户端通讯
int accsocfd = accept(socketfd, NULL, NULL);
// 处理消息
do_msg_process(accsocfd);
// 调用close来关闭与客户端的连接
close(accsocfd);
}
close(socketfd);
return 0;
}
运行结果
客户端
服务端