套接字数据类型
1. 流套接字
流套接字提供的是一个可靠、有序、双向字节的连接。从而发送的数据可以确保不会丢失、复制或乱序到达。流套接字有SOCK_STREAM指定,在AF_INET域中通过TCP/IP连接实现。
2. 数据报套接字
与流套接字相反,数据报套接字不建立和维持一个连接,其可以发送的数据报长度有限制。数据报作为一个单独的网络消息传输,可能会丢失、复制和乱序到达。数据报套接字由SOCK_DGRAM指定,在AF_INET域中通过UDP/IP连接实现。从资源的角度出发,其资源开销小,无需花费时间建立连接,速度较快。适用于信息服务中的单次查询,一般用来提供日常状态信息以及优先级低的日志记录。
程序基本流程
1. 服务器
- 配置网络地址信息
- 创建套接字
- 命名套接字
- 创建套接字队列
- loop{ 服务器接受连接 、 处理消息内容 (read,write) }
- 关闭套接字
2. 客户端
- 配置网络地址信息
- 创建套接字
- 设置套接字属性
- loop{ 连接套接字、 处理消息内容 (read,write)}
- 关闭套接字
创建套接字
/* Create a new socket of type TYPE in domain DOMAIN, using
protocol PROTOCOL. If PROTOCOL is zero, one is chosen automatically.
Returns a file descriptor for the new socket, or -1 for errors. */
int socket (int __domain, int __type, int __protocol) __THROW;
常用套接字域:AF_INET(网络套接字)、PF_UNIX(文件套接字,本地套接字)
常用套接字通信类型:SOCK_STREAM(tcp)、SOCK_DGRAM(udp)
协议类型:一般由套接字域和通信类型决定,常设置为0表示使用默认协议。
socket调用返回一个描述符,通过close结束套接字连接。
网络套接字地址:
struct in_addr
{
in_addr_t s_addr; /* ip address */
};
/* Structure describing an Internet socket address. */
struct sockaddr_in
{
__SOCKADDR_COMMON (sin_); /* sa_family_t sin_family */
in_port_t sin_port; /* Port number. */
struct in_addr sin_addr; /* Internet address. */
/* Pad to size of `struct sockaddr'. */
unsigned char sin_zero[sizeof (struct sockaddr) -
__SOCKADDR_COMMON_SIZE -
sizeof (in_port_t) -
sizeof (struct in_addr)];
};
// 域(sin_family):AF_INET(网络套接字)、PF_UNIX(文件套接字,本地套接字)
// 端口号(sin_port)
// 地址(sin_addr)
命名套接字:
要想通过socket调用创建的套接字可以被其他进程使用,服务器必须给该套接字命名。AF_INET套接字将关联到一个具体的端口。
/* Give the socket FD the local address ADDR (which is LEN bytes long). */
extern int bind (int __fd, const struct sockaddr * __addr, socklen_t __len)__THROW;
// __fd:套接字描述符
// __addr:套接字地址
创建套接字队列:
为能够使服务器套接字接受进入的连接,服务器必须创建一个队列来保存未处理的请求。
/* Prepare to accept connections on socket FD.
N connection requests will be queued before further requests are refused.
Returns 0 on success, -1 for errors. */
extern int listen (int __fd, int __n) __THROW;
// __fd: 套接字描述符
// n: 待处理客户端个数不因超过这个数字。
服务器接受连接:
只有当有客户程序试图连接指定的套接字时才返回。一般可以通过子进程或者多线程实现多客户,或者也可以使用select代替。
/* Await a connection on socket FD.
When a connection arrives, open a new socket to communicate with it,
set *ADDR (which is *ADDR_LEN bytes long) to the address of the connecting
peer and *ADDR_LEN to the address's actual length, and return the
new socket's descriptor, or -1 for errors.
This function is a cancellation point and therefore not marked with
__THROW. */
extern int accept (int __fd, __SOCKADDR_ARG __addr,
socklen_t *__restrict __addr_len);
客户请求连接:
/* Open a connection on socket FD to peer at ADDR (which LEN bytes long).
For connectionless socket types, just set the default address to send to
and the only address from which to accept transmissions.
Return 0 on success, -1 for errors.
This function is a cancellation point and therefore not marked with
__THROW. */
extern int connect (int __fd, const struct sockaddr * __addr, socklen_t __len);
设置套接字属性
/* Set socket FD's option OPTNAME at protocol level LEVEL
to *OPTVAL (which is OPTLEN bytes long).
Returns 0 on success, -1 for errors. */
int setsockopt (int __fd, int __level, int __optname,
const void *__optval, socklen_t __optlen) __THROW;
// fd : 套接字描述符
// level : 指定控制套接字的层次.可以取三种值:
// 1)SOL_SOCKET:通用套接字选项.
// 2)IPPROTO_IP:IP选项.
// 3)IPPROTO_TCP:TCP选项.
// optval : 获得或者是设置套接字选项.根据选项名称的数据类型进行转换
// optval:对于getsockopt(),指向返回选项值的缓冲。对于setsockopt(),指向包含新选项值的缓冲。
// optlen:对于getsockopt(),作为入口参数时,选项值的最大长度。作为出口参数时,选项值的实际长度。对于setsockopt(),现选项的长度。
网络节序:
由于不同类型电脑传递套接字端口号和地址的节序不一定一致,导致无法建立连接。因此,需要定义网络节序,保证客户与服务器连接传输。
// 长整形从主机节序到网络节序转换
static inline uint32_t htonl(uint32_t hostvar) { return hostvar; }
// 长整形从网络节序到主机节序转换
static inline uint32_t ntohl(uint32_t netvar) { return netvar; }
// 短整形从主机节序到网络节序转换
static inline uint16_t htons(uint16_t hostvar) { return hostvar; }
// 短整形从网络节序到主机节序转换
static inline uint16_t ntohs(uint16_t netvar) { return netvar; }
用法:
sSocketClient.addr_client.sin_addr.s_addr = htonl(INADDR_ANY);
sSocketClient.addr_client.sin_port = htons(iPort)
demo
服务器:
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
int server_sockfd, client_sockfd;
int server_len, client_len;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
int ret;
unlink("server_socket");
server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
// 命名套接字
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
server_addr.sin_port = htons(9734);
server_len = sizeof(server_addr);
ret = bind(server_sockfd, (struct sockaddr *)&server_addr, server_len);
if(ret != 0){
perror("bind ");
exit(-1);
}
// 创建连接队列
listen(server_sockfd, 5);
while (1)
{
char ch;
printf("server waiting.\n");
client_len = sizeof(client_addr);
client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_addr, (socklen_t *)&client_len);
read(client_sockfd, &ch, sizeof(ch));
printf("%c\n",ch);
ch ++;
write(client_sockfd,&ch,sizeof(ch));
close(client_sockfd);
}
return 0;
}
客户端:
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
int sockfd;
int len;
struct sockaddr_in addr;
int result;
char ch = 'A';
sockfd = socket(AF_INET, SOCK_STREAM, 0);
// 套接字命名
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
addr.sin_port = htons(9734);
len = sizeof(addr);
result = connect(sockfd, (struct sockaddr*)&addr, len);
if(result == -1){
perror("socket client connect ");
exit(-1);
}
write(sockfd, &ch, sizeof(ch));
read(sockfd, &ch, sizeof(ch));
printf("char form server = %c.\n",ch);
close(sockfd);
return 0;
}