Linux编程基础——Socket编程

Linux下的Socket编程大体上包括Tcp Socket、Udp Socket即Unix Domain Socket这三种,其中TCP和UDP方式的Socket编程用于编写应用层的socket程序,是我们用得比较多的,Unix Domain Socket主要用于unix的本地通信

TCP Socket

基于TCP协议的客户端/服务器程序的一般流程一般如下:

Linux编程基础——Socket编程_第1张图片

它基本上可以分为三个部分:

一、建立连接:

  1. 服务器调用socket()bind()listen()完成初始化后,调用accept()阻塞等待,处于监听端口的状态
  2. 客户端调用socket()初始化后,调用connect()发出SYN段并阻塞等待服务器应答
  3. 服务器应答一个SYN-ACK段,客户端收到后从connect()返回,同时应答一个ACK段,服务器收到后从accept()返回。

二、传输数据:

建立连接后,TCP协议提供全双工的通信管道,服务器端和客户端根据协议可以通过read和write的反复调用实现数据的传输

三、关闭连接:

当数据传输已经完成后,服务器和客户端可以调用Close关闭连接,一端关闭连接后,另一端read函数则会返回0,可以根据这个特征来感应另一端的退出。

下面就以一个简单的EchoServer演示一下如何创建服务器端和客户端代码,其中和socket相关api都会高亮显示。

服务器端步骤:

   1. socket(int domain,int type,int protocol):建立套接字;

   2 .bind(int sockid,struct sockaddr *addrp,socklen_t addrlen):把本机地址和端口跟上一步建立的socket绑定在一起;

   3. listen(int sockid,int qsize):监听某套接字;

   4. fd=accept(int sockid,struct sockaddr *callerid,socklen_t *addrlenp):等待某套接字接收信息;

   5. read(int fd,void *buf,size_t nbytes):从套接字接收数据;

   6. close(fd) close(sockid)

#include <iostream>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>

using namespace std;
 
#define MAXLINE 80
#define SERV_PORT 8000
 
int main()
{
 	//设置一个socket地址结构server_addr,代表服务器internet地址, 端口
    struct sockaddr_in servaddr, cliaddr;
    socklen_t cliaddr_len;
    int listenfd, connfd;
    char buf[MAXLINE];
    char str[INET_ADDRSTRLEN];

    //创建用于internet的流协议(TCP)socket,用server_socket代表服务器socket
    listenfd = socket(AF_INET, SOCK_STREAM, 0);

    bzero(&servaddr, sizeof(servaddr));//把一段内存区的内容全部设置为0
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERV_PORT);

    bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

    listen(listenfd, 20);

    cout<<"Accepting connections ..."<<endl;
    cliaddr_len = sizeof(cliaddr);
    
    while (1) 
    {
       connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
       int n = read(connfd, buf, MAXLINE);
       if(n>0){
          cout<<"received from "<<inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str))
          <<" at PORT "<<cliaddr.sin_port<<":"<<buf<<endl;
       }
         
       for (int i = 0; i < n; i++)//将从client接收到的字母转化为大写,回送给client
               buf[i] = toupper(buf[i]);
       n = write(connfd, buf, sizeof(buf));
    }
    close(connfd);
    close(listenfd);
 }

客户端步骤:

   1. socket():建立套接字;

   2.connect(int sockid,struct sockaddr *serv_addrp,socklen_t addrlen):连接到服务器;

   3. write(int sockfd,const void *buf,size_t nbytes):发送数据到服务器.

   4. close(sockid);

#include <iostream>
#include <string.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>

using namespace std;

#define MAXLINE 80
#define SERV_PORT 8000

int main(int argc, char *argv[])
{
    char buf[MAXLINE];

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);

    sockaddr_in servaddr = {0};
    servaddr.sin_family = AF_INET;
    inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
    servaddr.sin_port = htons(SERV_PORT);

    if (0 != connect(sockfd, (sockaddr *)&servaddr, sizeof(servaddr)))
    {
        cout<<"connected failed"<<endl;
        return 1;
    }

    char message[20];
    cin>>message;
    
	int count = write(sockfd, message, sizeof(message));
	if(count > 0){
	  	cout<<"send to server:"<<message<<endl;
	}else{
	  	cout<<"fail send to server"<<endl;
	  	return 1;
	}
	  
	count = read(sockfd, buf, sizeof(message));
	if(count > 0){
	  	cout<<"response from server: "<<buf<<endl;
	}
    close(sockfd);
    return 0;
}

UDP Socket

典型的UDP客户端/服务器通讯过程如下图所示:

Linux编程基础——Socket编程_第2张图片

由于UDP不需要维护连接,程序逻辑简单了很多,但是UDP协议是不可靠的,实际上有很多保证通讯可靠性的机制需要在应用层实现,可能反而会需要更多代码。

服务端步骤:

1:加载套接字库,创建套接字(socket())

2:绑定套接字到一个IP地址和一个端口上(bind())

3:等待和接收数据(sendto()/recvfrom())

4:关闭套接字,关闭加载的套接字库 (close())

#include <iostream>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>

using namespace std;
 
#define MAXLINE 80
#define SERV_PORT 8888
 
int main()
{
 	  //设置一个socket地址结构server_addr,代表服务器internet地址, 端口
    struct sockaddr_in servaddr;
    char buf[MAXLINE];
    char str[INET_ADDRSTRLEN];

    //创建用于internet的数据报协议(UDP)socket,用server_socket代表服务器socket
    int serverfd = socket(AF_INET, SOCK_DGRAM, 0);

    bzero(&servaddr, sizeof(servaddr));//把一段内存区的内容全部设置为0
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERV_PORT);
    
    int ret = bind(serverfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
    if(ret < 0){
        cout<<"fail bind"<<endl;
        return 0;
    }
    
    /* 定义一个地址,用于捕获客户端地址 */
    struct sockaddr_in client_addr;
	socklen_t client_addr_length = sizeof(client_addr);

    while (1) 
    {   	
       int n = recvfrom(serverfd,buf,sizeof(buf),0,(struct sockaddr *)&client_addr,&client_addr_length);
       if(n>0){
          cout<<"received from "<<inet_ntop(AF_INET, &client_addr.sin_addr, str, sizeof(str))
          <<" at PORT "<<client_addr.sin_port<<":"<<buf<<endl;
          for(int i = 0; i < n; i++)//将从client接收到的字母转化为大写,回送给client
               buf[i] = toupper(buf[i]);
          sendto(serverfd,buf,sizeof(buf),0,(struct sockaddr *)&client_addr,sizeof(client_addr));
       }
    }
    close(serverfd);
    return 0;
}

客户端步骤:

1:创建一个套接字(socket)

2:向服务器发送数据(sendto);

3:关闭套接字;
#include <iostream>
#include <string.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>

using namespace std;

#define MAXLINE 80
#define SERV_PORT 8888

int main(int argc, char *argv[])
{
    char buf[MAXLINE];

    /* 服务端地址 */
    sockaddr_in servaddr = {0};
    servaddr.sin_family = AF_INET;
    inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
    servaddr.sin_port = htons(SERV_PORT);

    //定义一个client socket
    int client_fd = socket(AF_INET, SOCK_DGRAM, 0);

    cin>>buf;
    int count = sendto(client_fd,buf,sizeof(buf),0,(struct sockaddr *)&servaddr,sizeof(servaddr));
    if(count>0){
    	cout<<"success send to server"<<endl;
        socklen_t server_add_len = sizeof(servaddr);
    	int n = recvfrom(client_fd,buf,sizeof(buf),0,(struct sockaddr *)&servaddr,&server_add_len);
       	if(n>0){
          cout<<"response from server:"<<buf<<endl;
       	}
    }else{
    	cout<<"fail send to server"<<endl;
    } 
    close(client_fd);
    return 0;
}

UNIX Socket

socket API原本是为网络通讯设计的,但后来在socket的框架上发展出一种IPC机制,就是UNIXDomain Socket。虽然网络socket也可用于同一台主机的进程间通讯(通过loopback地址127.0.0.1),但是UNIX Domain Socket用于IPC更有效率:不需要经过网络协议栈,不需要打包拆包、计算校验和、维护序号和应答等,只是将应用层数据从一个进程拷贝到另一个进程。这是因为,IPC机制本质上是可靠的通讯,而网络协议是为不可靠的通讯设计的。UNIX Domain Socket也提供面向流和面向数据包两种API接口,类似于TCP和UDP,但是面向消息的UNIX Domain Socket也是可靠的,消息既不会丢失也不会顺序错乱。

使用UNIX Domain Socket的过程和网络socket十分相似,也要先调用socket()创建一个socket文件描述符,address family指定为AF_UNIX,type可以选择SOCK_DGRAM或SOCK_STREAM,protocol参数仍然指定为0即可。

UNIX Domain Socket与网络socket编程最明显的不同在于地址格式不同,用结构体sockaddr_un表示,网络编程的socket地址是IP地址加端口号,而UNIX Domain Socket的地址是一个socket类型的文件在文件系统中的路径,这个socket文件由bind()调用创建,如果调用bind()时该文件已存在,则bind()错误返回。

下面是unix udp通信的例子:
服务器端:
#include <iostream>
#include <sys/types.h>  
#include <sys/socket.h>  
#include <sys/un.h>  
#include <unistd.h>  
#include <stdlib.h>  
#include <stdio.h>  
using namespace std;

#define MAXLINE 60  

int main()  
{  
  /* delete the socket file */
  unlink("server_socket");
    
  /* create a UNIX socket */  
  int serverfd = socket(AF_UNIX, SOCK_DGRAM, 0);
    
  struct sockaddr_un server_addr;  
  server_addr.sun_family = AF_UNIX;  
  strcpy(server_addr.sun_path, "server_socket");
    
  /* bind with the local file */  
  bind(serverfd, (struct sockaddr *)&server_addr, sizeof(server_addr));     
    
  char buf[MAXLINE];
  struct sockaddr_un client_addr;  
  socklen_t len = sizeof(client_addr);  

  while(1)  
  {  
    int n = recvfrom(serverfd,buf,sizeof(buf),0,(struct sockaddr *)&client_addr,&len);
    if(n>0){
      cout<<"received:"<<buf<<endl;
    } 
  }  
  close(serverfd);  
  return 0;  
}  
客户端:
#include <iostream>
#include <sys/types.h>  
#include <sys/socket.h>  
#include <sys/un.h>  
#include <unistd.h>  
#include <stdlib.h>  
#include <stdio.h>  
using namespace std;

#define MAXLINE 60 
  
int main()  
{  
    /* create a socket */  
    int client_fd = socket(AF_UNIX, SOCK_DGRAM, 0);  
    
    /*server address*/
    struct sockaddr_un servaddr;  
    servaddr.sun_family = AF_UNIX;  
    strcpy(servaddr.sun_path, "server_socket");
    
    char buf[MAXLINE];
    cin>>buf;

    int count = sendto(client_fd,buf,sizeof(buf),0,(struct sockaddr *)&servaddr,sizeof(servaddr));  
    if(count>0){
      cout<<"send success:"<<buf<<endl;
    }
       
    /* close the socket */  
    close(client_fd);     
    return 0;  
}  




你可能感兴趣的:(Linux编程基础——Socket编程)