Socket编程(一)

概念和基本操作

Posted by Gavin on November 18, 2019

自觉此心无一事

小鱼跳出绿萍中

概念

Socket

Socket原指IP地址和端口对(ip,port),是TCP通信的一端,其唯一标志一个通信端。在Linux系统中,与socket相关的api大多封装在sys/socket.h头文件中,包括创建socket命名socket监听socket接受连接发起连接读写数据 等等。

字节序

现代CPU的累加器一次至少装载4字节,这4字节在内存中排列的顺序就是字节序。字节序分为大端字节序小端字节序。大端序指的是一个整数的高位字节(23~31bit)存储在内存低地址处,低位字节(0~7bit)存储在内存的高地址处;小端序正好相反。

PS:现代PC大多采用小端序,因此小端序又叫主机字节序;大端序又叫网络字节序,默认发送端采用大端序

协议族和地址族

  1. 协议族和地址族的关系

    协议族 地址族 描述
    PF_UNIX AF_UNIX UNIX本地协议族
    PF_INET AF_INET TCP/IPv4协议族
    PF_INET6 AF_INET6 TCP/IPv6协议族
  2. 协议族及其地址值

    协议族 地址值含义和长度
    PF_UNIX 文件路径名,可达108字节
    PF_INET 16bit端口号和32bit IPv4地址;6字节
    PF_INET6 16bit端口号,32bit流标志,128bit IPv6地址,32bit范围ID;26字节

C/S通信

  • 创建socket

      #include <sys/types.h>
      #include <sys/socket.h>
      int socket(int domain,int type,int protocol)
    
    • domain指定协议族(PF_UNIX、PF_INET、PF_INET6)
    • type指定服务类型(SOCK_STREAM、SOCK_UGRAM)
    • protocol选择一个具体协议,默认为0

    创建成功则返回一个socket文件描述符,失败则返回-1

  • 绑定socket

      #include <sys/types.h>
      #include <sys/socket.h>
      int bind(int sockfd,const struct sockaddr* addr,socklen_t addrlen)
    

    创建socket时,我们指定了地址族,但并未指定具体哪个socket地址,因此需要将socket与socket地址绑定

    • sockfd是socket文件描述符
    • addr是地址
    • addrlen说明地址长度

    绑定成功返回0,失败返回-1,常见error是:

    • EACCESS(绑定受保护地址,0~1023)
    • EADDRINUSE(绑定被占用地址)
  • 监听socket

      #include <sys/socket.h>
      int listen(int sockfd,int backlog)
    

    backlog是内核监听队列的最大长度,监听队列长度如果超过最大长度,服务器将不再监听,典型值是5

  • 接受连接

      #include <sys/types.h>
      #include <sys/socket.h>
      int accept(int sockfd,struct sockaddr* addr, socklen_t addrlen)
    

    成功时返回一个新的连接socket,服务器可通过此socket与客户端通信,失败返回-1

  • 发起连接

      #include <sys/types.h>
      #include <sys/socket.h>
      int connect(int sockfd,struct sockaddr* addr, socklen_t addrlen)
    
  • 关闭连接

      #include <unistd.h>
      int close(int fd)
    

    fd是待关闭的socket描述符,不过close系统调用并非立即关闭一个连接,而是给该fd的引用计数器-1,直到为0,才关闭,若要立即终止,可以使用shutdown系统调用

      #include <sys/socket.h>
      int shutdown(int sockfd,int howto)
    

    howto参数:

    含义
    SHUT_RD 关闭读的这一半,接收缓冲区数据丢失
    SHUT_WR 关闭写的这一半,发送缓冲区会被全部发送出去
    SHUT_RDWR 全部关闭
  • TCP数据读写

      #include <sys/types.h>
      #include <sys/socket.h>
      ssize_t recv(int sockfd, void* buf, int flags)
      ssize_t send(int sockfd, const void* buf, size_t len, int flags)
    
  • UDP数据读写

      #include <sys/types.h>
      #include <sys/socket.h>
      ssize_t recvfrom(int sockfd, void* buf, int flags,struct sockaddr* src_addr, socklen_t* addrlen)
      ssize_t sendto(int sockfd, const void* buf, size_t len, int flags, const sockaddr* dest_addr, socklen_t addrlen)
    
  • 通用数据读写

      #include <sys/socket.h>
      ssize_t recvmsg(int sockfd, struct msghdr* msg, int flags)
      ssize_t sendmsg(int sockfd, struct msghdr* msg, size_t len, int flags)
    

基本操作

server

#include <iostream>
#include <string>

#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>

using namespace std;

int main(int argc, char const *argv[])
{
	int sock = socket(PF_INET, SOCK_STREAM, 0);
	if (sock == -1)
	{
		cout << "create socket failed..." << endl;
		return -1;
	}
	cout << "create socket successed..." << endl;

	struct sockaddr_in addrin;
	addrin.sin_family = PF_INET;
	addrin.sin_port = 8080;
	addrin.sin_addr.s_addr = inet_addr("127.0.0.1");

	int bind_status = bind(sock, (struct sockaddr*)&addrin, sizeof(addrin));
	if (bind_status == -1)
	{
		cout << "bind socket failed..." << endl;
		return -1;
	}
	cout << "bind socket successed..." << endl;

	int listen_status = listen(sock,5);
	if (listen_status == -1)
	{	
		cout << "listen socket failed..." << endl;
		return -1;
	}
	cout << "listen socket successed..." << endl;

	socklen_t len = sizeof(addrin);
	int sockfd = accept(sock, (struct sockaddr*)&addrin, &len);
	if (sockfd == -1)
	{
		cout << "accept socket failed..." << endl;
		return -1;
	}
	cout << "accept socket successed..." << endl;

	string buf(512,'\0');
	int recv_status = recv(sockfd, const_cast<char*>(buf.c_str()), 512, 0);
	if (recv_status == -1)
	{
		cout << "receive data from socket failed..." << endl;
		return -1;
	}
	cout << "receive data from socket successed..." << endl;

	cout << buf << endl;

	close(sockfd);
	close(sock);

	return 0;
}

client

#include <iostream>
#include <string>

#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>

using namespace std;

int main(int argc, char const *argv[])
{
	int sock = socket(PF_INET, SOCK_STREAM, 0);
	if (sock == -1)
	{
		cout << "create socket failed..." << endl;
		return -1;
	}
	cout << "create socket successed..." << endl;

	struct sockaddr_in addrin;
	addrin.sin_family = PF_INET;
	addrin.sin_port = 8080;
	addrin.sin_addr.s_addr = inet_addr("127.0.0.1");

	int connect_status = connect(sock, (struct sockaddr*)&addrin, sizeof(addrin));
	if (connect_status == -1)
	{
		cout << "connect socket failed..." << endl;
		return -1;
	}
	cout << "connect socket successed..." << endl;

	string data = "Hello World";
	int send_status = send(sock,data.c_str(),data.length(),0);
	if (send_status == -1)
	{
		cout << "send data to socket failed..." << endl;
		return -1;
	}
	cout << "send data to socket successed..." << endl;

	close(sock);

	return 0;
}