套接字的基本操作有:
创建(socket)、命名(bind)、侦听(listen)、连接(accept)、关闭(shutdown)、发送(send)、接受(recv)。
下面逐个分析:
一、创建(socket):
函数原型:int socket(int domain, int type, int protocol);
参数:
domain:指定发送通信的域
可取值:AF_UNIX:本地主机通信,与IPC类似
AF_INET:Internet地址IPV4协议
type:指定通信类型
可取值:SOCK_STREAM(流套接字)、SOCK_DGRAM(数据报套接字)、SOCK_RAW(原始套接字)
protocol:指定该套接字描述符上的一个特殊的协议,如TCP,UDP等,一般设为0
返回值:
成功:返回创建的套接字描述符
失败:-1
补充:SOCK_STREAM(流套接字)应用TCP协议,提供顺序的,可靠的,基于字节流的双向链接
SOCK_DGRAM(数据报套接字)应用UDP协议,无链接,不可靠,不固定
SOCK_RAW(原始套接字)提供访问互联网协议和Internal Network Interfaces的权限,只有超级用户才可使用。
二、命名(bind)
函数原型:int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数:
sockfd:套接字描述符
addr:指向通用套接字的协议地址结构,包括协议、地址和端口等信息
addrlen:协议地址结构的长度,一般为sizeof(sockaddr_in)
但是,一般情况addr这个参数并不采用struct sockaddr *类型,而是struct sockaddr_in,使用时要注意强制类型转换。看看struct sockaddr_in的成员:
struct sockaddr_in {
short sin_family; //16位地址协议族
u_short sin_port; //16位端口地址
struct in_addr sin_addr; //32位IP地址 unsigned char sin_zero[8] //使结构sockaddr_in与sockaddr长度相同
};
struct in_addr {
u_long s_addr;
};
该结构中描述IP的是一个32位整型变量,而我们平时所用的是由”.“隔开的字符串。二者之间相互转换参照这几个函数,具体使用方法参照man命令
unsigned long inet_addr(const char *cp); int inet_aton(const char *cp, struct in_addr *inp); char *inet_ntoa(struct in_addr in);
网络通信中数据存储采用网络字节序,因此要进行主机字节序与网络字节序之间的相互转化,参照以下函数
uint32_t htonl(uint32_t hostlong); uint16_t htons(uint16_t hostshort); uint32_t ntohl(uint32_t netlong); uint16_t ntohs(uint16_t netshort);
返回值:
成功:0
失败:-1
三、侦听(listen)
函数原型:int listen(int sockfd, int backlog);
参数:
sockfd:用socket创建的套接字描述符
backlog:sockfd接收连接的最大数目
返回值:
成功:0
失败:-1
TCP通信模型中,服务器端要完成创建、命名和侦听后才能调用accept接收客户端请求,为了提高代码重用度,这里将以上三步进行封装,代码如下:
/**************************************
函数名:CreateSock
参数:
pSock:回传创建的侦听套接字描述符
nPort:指定套接字侦听端口
nMax:该套接字最大连接数
函数功能:封装套接字的创建、命名和侦听
返回值:0
**************************************/
int CreateSock(int *pSock, int nPort , int nMax)
{
struct sockaddr_in addrin;
struct sockaddr *paddr = (struct sockaddr*)&addrin; assert(pSock != NULL && nPort > && nMax > );
/*清空addrin*/
memset(&addrin, ,sizeof(addrin)); addrin.sin_family = AF_INET;
addrin.sin_addr.s_addr = htonl(INADDR_ANY);
addrin.sin_port = htons(nPort); /*创建TCP套接字描述符*/
*pSock = socket(AF_INET, SOCK_STREAM, ); /*命名套接字*/
bind(*pSock, paddr, sizeof(addrin)); /*进入侦听状态*/
listen(*pSock, nMax); return ;
}
四、连接(accept)
函数原型:int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数:
sockfd:用socket创建的套接字描述符
addr:指向通用套接字的协议地址结构,包括协议、地址和端口等信息
addrlen:协议地址结构的长度,一般为sizeof(sockaddr_in)
返回值:
成功:创造返回一个新的socket与客户进程通信,原sockfd仍用于套接字侦听。
这里再封装一个函数,将accept也加入其中
/**************************************
函数名:AcceptSock
参数:
pSock:创建的新的套接字描述符与客户
进程通信
nSock:accept成功后依然用于套接字侦听
函数功能:接受客户端的套接字连接申请
返回值:0
**************************************/
int AcceptSock(int *pSock, int nSock)
{
struct sockaddr_in addrin;
int lSize;
assert(pSock != NULL && nSock > );
while()
{
lSize = sizeof(addrin);
memset(&addrin, , sizeof(addrin));
if((*pSock = accept(nSock, (struct sockaddr*)&addrin, &lSize)) > )
return ; else
assert();
}
}
五、接收(recv)
函数原型:ssize_t recv(int sockfd, void *buf, size_t len, int flags);
参数:
sockfd:与远程通信连接的套接字描述符
buf:接收数据的缓冲区地址
len:缓冲区长度
flags:接收标志
取值:MSG_OOB、MSG_PEEK或MSG_WAITALL
有了以上知识,我们就可以用socket进行简易通讯了,本处设计一个服务器端程序的例子,创建socket,与客户端建立连接并打印收到的数据。代码如下:
头文件:socket.h
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <assert.h>
#include <string.h>
#include <arpa/inet.h> /**************************************
函数名:CreateSock
参数:
pSock:回传创建的侦听套接字描述符
nPort:指定套接字侦听端口
nMax:该套接字最大连接数
函数功能:封装套接字的创建、命名和侦听
返回值:0
**************************************/
int CreateSock(int *pSock, int nPort , int nMax)
{
struct sockaddr_in addrin;
struct sockaddr *paddr = (struct sockaddr*)&addrin; assert(pSock != NULL && nPort > && nMax > );
memset(&addrin, ,sizeof(addrin)); addrin.sin_family = AF_INET;
addrin.sin_addr.s_addr = htonl(INADDR_ANY);
addrin.sin_port = htons(nPort); /*创建TCP套接字描述符*/
*pSock = socket(AF_INET, SOCK_STREAM, ); /*命名套接字*/
bind(*pSock, paddr, sizeof(addrin)); /*进入侦听状态*/
listen(*pSock, nMax); return ;
} /**************************************
函数名:AcceptSock
参数:
pSock:创建的新的套接字描述符与客户
进程通信
nSock:accept成功后依然用于套接字侦听
函数功能:接受客户端的套接字连接申请
返回值:0
**************************************/ int AcceptSock(int *pSock, int nSock)
{
struct sockaddr_in addrin;
int lSize;
assert(pSock != NULL && nSock > );
while()
{
lSize = sizeof(addrin);
memset(&addrin, , sizeof(addrin));
if((*pSock = accept(nSock, (struct sockaddr*)&addrin, &lSize)) > )
return ; else
assert();
}
}
主程序:
#include <stdio.h>
#include "socket.h" int main()
{
int nSock,pSock;
char buf[]; CreateSock(&nSock, , ); AcceptSock(&pSock, nSock); memset(buf, , sizeof(buf)); //初始化缓冲区 recv(pSock, buf, sizeof(buf), ); fprintf(stderr, buf); //打印接收到的数据 close(pSock); close(nSock); return ;
}
由于接收函数recv默认以阻塞方式读取数据,所以未读到数据进程会进入阻塞状态。
1、编译好可执行程序后,执行
2、另开一个终端,查看套接字连接情况
命令:netstat -an|grep 9001
3、打开浏览器,地址栏输入xxx.xxx.xxx.xxx:9001,xxx为UNIX系统的IP地址,要确保浏览器与UNIX能正常通信
4、进程收到数据后会打印出来
这次先记到这里,其他的基本操作以后用到了再做记录。
如果有疑问或错误,欢迎指出