Linux网络编程基础API【socket】

文章目录

      • 创建socket
      • 命名socket
      • 监听socket
      • 接受连接
      • 发起连接
      • 关闭连接
      • 代码示例

创建socket

linux的思想是一切东西皆为文件,socket也不例外,他就是个可读可写可控制可关闭的文件描述符。接下来我们看看如何创建一个socket。

#include
#include

int sockfd=socket(int domain,int type,int protocol);	//创建套接字  
assert(sockfd!=-1);		//判断套接字是否创建成功 若失败返回-1.

【1】domain指定系统使用的哪个协议簇,如下图所示
Linux网络编程基础API【socket】_第1张图片【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地址。

命名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

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如下:

  • ECONNREFUSED:目标端口不存在,连接被拒绝。
  • ETIMEDOUT:连接超时。

关闭连接

关闭连接实际上就是关闭相对应的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的操作。如下:

  • SHUT_RD 表示关闭读端,并且该socket的接收缓冲区中的数据都将被丢弃,程序不能再针对该sockfd文件描述符进行执行操作。
  • SHUT_WR 表示关闭写端,sockfd的发送缓冲区中的数据会在真正关闭连接前全部发送出去 ,程序不能再针对该sockfd文件描述符进行执行操作。
  • SHUT_RDWR 同时关闭sockfd上的读和写

由此可见: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);
}

运行结果如下:
Linux网络编程基础API【socket】_第2张图片
Linux网络编程基础API【socket】_第3张图片

你可能感兴趣的:(网络编程,linux)