linux的思想是一切东西皆为文件,socket也不例外,他就是个可读可写可控制可关闭的文件描述符。接下来我们看看如何创建一个socket。
#include
#include
int sockfd=socket(int domain,int type,int protocol); //创建套接字
assert(sockfd!=-1); //判断套接字是否创建成功 若失败返回-1.
【1】domain指定系统使用的哪个协议簇,如下图所示
【2】type参数指定的是服务类型,服务类型主要有SOCK_STREAM服务(流服务)和SOCK_UGRAM(数据报)服务,对于TCP/IP协议簇来说,值为SOCK_STREAM时表示传输层使用TCP协议,值为SOCK_UGRAM时表示传输层使用UDP协议
【3】protocol参数是指在前两个参数构成的协议集合下,再选一个具体的协议,不过这个值通常都是唯一的,因为前两个参数已经完全决定了它的值。几乎在所有情况下,他都应该被设置成0,表示使用默认的协议。
返回值:select系统调用成功时返回一个文件描述符,失败时返回-1并且设置errno
创建socket时我们给定了地址簇,但是并未指定该地址簇具体使用哪个socket地址。
将一个socket文件描述符与socket地址绑定成为给socket命名
在一个服务器程序中,我们通常要命名socket因为只有命名后客户端才能够知道如何连接它
而客户端一般不用命名socket,而是采取匿名方式即使用操作系统默认分配的socket地址。
命名socket使用bind系统调用
#include
#include
int bind(int sockfd,const struct sockaddr* my_addr,socklen_t length);
bind将my_addr所指的socket地址分配给未命名的sockfd文件描述符,length参数表示该地址的长度
返回值:bind()成功时返回0,失败时返回-1并设置errno。
socket被命名后还不能马上接受连接,因为他还需要一个监听队列用来存放待处理的客户端请求。
#include
int listen(int sockfd,int backlog);
sockfd指的是被监听的socket
backlog表示内核监听队列能接受的最大的监听数目,如果监听队列的长度超过backlog,服务器就不会再接收新的客户端连接,客户端也将收到ECONNREFUSED错误信息
一般情况下把参数backlog的参数值设为5。
返回值:listen成功时返回0,失败时返回-1并设置errno
accept()系统调用表示从listen的监听队列中接受一个连接
#include
#include
int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);
sockfd参数是执行过系统调用listen的监听socket。
addr参数用来获取被接收连接的远端socket地址,该socket地址的长度由addrlen指出。
accept成功时返回一个新的连接socket,该socket唯一标识了被接受的这个连接。
服务器可以通过读写这个socket来与被接收连接对应的客户端进行通信。
返回值:accept失败时返回-1并设置errno。
如果说服务器通过listen系统调用来被动接受连接,那么connect()系统调用则是主动与服务器建立连接。
#include
#include
int connect(int sockfd,const struct sockaddr* serv_addr,socklen_t addrlen);
sockfd由socket系统调用返回一个socket
serv_addr参数是服务器监听的socket地址
addrlen指定这个地址的长度
connect成功时返回0,一旦成功建立连接,sockfd就唯一标识了这个连接。
客户端就可以通过读写这个sockfd来与服务器通信。
connect失败时返回-1并设置errno
其中两种常见的errno如下:
关闭连接实际上就是关闭相对应的socket,可以通过如下的关闭普通文件描述符的系统调用来完成
#include
int close(int sockfd);
sockfd表示待关闭的socket。
注意:close并非总是立即关闭一个socket连接,而是将sockfd的引用计数减一,只有当sockfd的引用计数为0时才会真正关闭这个连接,在多进程程序中,一次fork系统调用默认将父进程中打开的sockfd引用计数加一,因此我们必须在父进程和子进程中都对sockfd进行close()操作才可以完全的将连接关闭。
如果无论如何都要中止这个sockfd的连接,而不单单是将他的引用计数减一,则可以使用shutdown系统调用(相对于close(),他就是专门为网络编程设计的)
#include
int shutdown(int sockfd,int howto);
其中sockfd表示待关闭的socket
howto有三个可取的值,指明了shutdown的操作。如下:
由此可见:close只能将socket的读和写同时关闭,而shutdown可以分边关上socket的读或者写或者都关上。
返回值:shutdown成功时返回0,失败时返回-1并设置errno。
server.c
#include
#include
#include
#include
#include
#include
#include
#include
int main()
{
int sockfd=socket(AF_INET,SOCK_STREAM,0); //创建套接字
assert(sockfd!=-1); //判断是否创建成功
struct sockaddr_in saddr,caddr; //创建套接字地址并初始化
memset(&saddr,0,sizeof(saddr));
saddr.sin_family=AF_INET;
saddr.sin_port=htons(6000);
saddr.sin_addr.s_addr=inet_addr("127.0.0.1");
int res=bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr)); //给套接字命名
assert(res!=-1);
listen(sockfd,5);//监听socket 最大监听数目为5 表示已经完成三次握手的队列长度
while(1)
{
int len=sizeof(caddr);
int c=accept(sockfd,(struct sockaddr*)&caddr,&len);//接受连接 sockfd为监听套接字
if(c<0) //c为链接套接字
{
continue;
}
printf("accept c=%d,ip=%s,port=%d\n",c,inet_ntoa(caddr.sin_addr),ntohs(caddr.sin_port));
//输出连接信息
while(1)
{
char buff[128] = {0};
int n = recv(c,buff,127,0);
if(n <= 0)
{
break;
}
printf("buff=%s\n",buff);
send(c,"OK",2,0);
}
printf("one client over\n");
close(c);
}
return 0;
}
client.c
#include
#include
#include
#include
#include
#include
#include
#include
int main()
{
int sockfd=socket(AF_INET,SOCK_STREAM,0); //创建套接字
assert(sockfd!=-1);
struct sockaddr_in saddr,caddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family=AF_INET;
saddr.sin_port=htons(6000);
saddr.sin_addr.s_addr=inet_addr("127.0.0.1");
int res=connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr)); //发起连接
assert(res!=-1); //判断是否连接成功
while(1)
{
char buff[128] = {0};
printf("Please Input:");
fgets(buff,128,stdin);
if(strncmp(buff,"end",3) == 0 )
{
break;
}
send(sockfd,buff,strlen(buff),0);
memset(buff,0,128);
recv(sockfd,buff,127,0);
printf("buff[%d]:%s\n",n,buff);
printf("\n");
}
close(sockfd); //关闭文件描述符
exit(0);
}