Linux系统编程2:Socket编程的接口介绍

1. 接口

1.1 转换操作

转换操作主要分为三类:字节序转换操作、IP地址转换操作和主机名转换操作。

1.1.1 字节序转换操作

  • 网络序转主机序
No.函数含义作用
1ntohs()network to host short把unsigned short类型从网络序转换到主机序
2ntohl()network to host long把unsigned long类型从网络序转换到主机序
  • 主机序转网络序
No.函数含义作用
1htons()host to network short把unsigned short类型从主机序转换到网络序
2htonl() host to network long把unsigned long类型从主机序转换到网络序
  • trans.cpp
  1. 添加头文件
#include <arpa/inet.h> // 1. 添加头文件
    1. 主机序的小端转换为网络序的大端
  • 完整代码
#include <iostream>
#include <arpa/inet.h> // 1. 添加头文件
using namespace std;

int main(){
	short s = 0x1234;
	cout << hex << s << endl;
	cout << hex << htons(s) << endl; // 2. 把unsigned short类型从主机序转换到网络序
	cout << sizeof(long) << endl;
	int l = 0x123456;
	cout << hex << l << endl;
	cout << hex << htonl(l) << endl; // 3. 把unsigned long类型从主机序转换到网络序


}

1.1.2 IP地址转换操作

No.函数功能特点
1int inet_aton(const char string, struct in_addraddr)点分十进制数串转网络字节序长整型IPv4专用
2in_addr_t inet_addr(const char* string)点分十进制数串转网络字节序长整型IPv4专用
3char* inet_ntoa(struct in_addr addr)网络字节序长整型转点分十进制数串IPv4专用
4int inet_pton(int af, const char *src, void *dst)点分十进制数串转网络字节序长整型IPv4/IPv6通用(推荐)
5const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt)网络字节序长整型转点分十进制数串IPv4/IPv6通用(推荐)
  • 使用规范查询
man inet_aton

结构体

No.结构体功能特性
1struct sockaddr套接字地址结构IPv4/IPv6通用
2struct sockaddr_inIPv4套接字地址结构IPv4专用
3struct in_addrIPv4地址结构IPv4专用
4in_addr_tIPv4地址类型IPv4专用
5struct sockaddr_in6IPv6套接字地址结构IPv6专用
  • 套接字地址结构
struct sockaddr {
    unsigned short sa_family; // 套接字地址簇类型,为AF_INET
    char sa_data[14];         // 套接字地址数据(14位的协议地址)
};
  • IPv4套接字地址结构
struct sockaddr_in{
    short sin_family;         // 套接字地址簇类型,为AF_INET
    unsigned short sin_port;  // 端口号,网络字节序
    struct in_addr sin_addr;  // IP地址,网络字节序
    unsigned char sin_zero[8];// 填充字节
};

sin_zero[8]用来保证结构体struct sockaddr_in的大小和结构体struct sockaddr的大小相等。

  • IPv4地址结构
struct in_addr {
    in_addr_t s_addr;
};
  • IPv4地址类型
typedef unsigned int in_addr_t;
  • IPv6套接字地址结构
struct sockaddr_in6{
   uint8_t sin6_len;           //IPv6 为固定的24 字节长度
   sa_family_t sin6_family;    //套接字地址簇类型,为AF_INET6
   in_port_t sin6_port;        //16 位端口号,网络字节序
   uint32_t sin6_flowinfo;     //32 位流标签
   struct in6_addr sin6_addr;  //128 位IP地址
}
  • 案例:IPv4 点分十进制转网络序整型
#include <iostream>
#include <arpa/inet.h>
using namespace std;

int main(){
	struct in_addr addr;
	if(0 == inet_aton("192.168.0.1",&addr)){
		cout << "inet_aton error" << endl;
	}else{
		cout << hex << addr.s_addr << endl;
		cout << hex << 192 << endl;
		cout << hex << 168 << endl;
	}
	{
		struct in_addr addr;
		if(0 == inet_pton(AF_INET,"192.168.0.1",&addr)){
			cout << "inet_aton error" << endl;
		}else{
			cout << hex << addr.s_addr << endl;
		}
	}
	
	in_addr_t addr2 = inet_addr("192.168.0.1");
	if(INADDR_NONE == addr2){
		cout << "invalid ip" << endl;
	}else{
		cout << hex << addr2 << endl;
	}

	// IPv4网络序整型转成点分十进制字符串
	cout << inet_ntoa(addr) << endl;
	{
		char ip[16];
		inet_ntop(AF_INET,&addr,ip,sizeof(ip)); //AF_INET意思是使用IPv4协议 
		cout << ip << endl;
	}
}

1.1.2.1 IPv4专用

点分十进制数串转网络字节序长整型

①推荐方式

int inet_aton(const char *string, struct in_addr*addr)
  • 参数
No.参数含义
1string点分十进制IP地址字符串
2addr网络字节序长整型IP地址
  • 返回值
No.返回值含义
10失败
2非0成功
  • 示例
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
 
int main(int argc,char* argv[]){
    if(2 != argc){
        printf("usage:%s <ip>\n",argv[0]);
        return 1;
    }
    struct in_addr addr;
    int res = inet_aton(argv[1],&addr);
    if(0 == res){
        perror("inet_aton error");
        return 1;
    }
    printf("res:%d\n%s = 0x%08x",res,argv[1],addr);
 
    return 0;
}

①旧方式

in_addr_t inet_addr(const char* string)
  • 参数
No.参数含义
1string点分十进制IP地址字符串
  • 返回值
No.返回值含义
1INADDR_NONE失败
2非INADDR_NONE网络字节序长整型IP地址
  • 示例
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
 
int main(int argc,char* argv[]){
    if(2 != argc){
        printf("usage:%s <ip>\n",argv[0]);
        return 1;
    }
    in_addr_t addr = inet_addr(argv[1]);
    if(INADDR_NONE == addr){
        perror("inet_addr error");
        return 1;
    }
    printf("%s = 0x%08x",argv[1],addr);
 
    return 0;
}

网络字节序长整型转点分十进制数串

char* inet_ntoa(struct in_addr addr)
  • 参数
No.参数含义
1addr网络字节序长整型IP地址
  • 返回值
No.返回值含义
1非NULL点分十进制IP地址字符串
2NULL失败
  • 示例
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
 
int main(int argc,char* argv[]){
    if(2 != argc){
        printf("usage:%s <num>\n",argv[0]);
        return 1;
    }
    struct in_addr addr;
    addr.s_addr = strtol(argv[1],NULL,16);
    char* ip = inet_ntoa(addr);
    if(NULL == ip){
        perror("inet_ntoa error");
        return 1;
    }
    printf("0x%x = %s",addr.s_addr,ip);
 
    return 0;
}

1.1.2.2 IPv4/IPv6通用(推荐)

点分十进制数串转网络字节序长整型

int inet_pton(int af, const char *src, void *dst)
  • 参数
No.参数含义
1af地址族。AF_INET/AF_INET6
2src点分十进制IP地址字符串
3dst网络字节序长整型IP地址
  • 返回值
No.返回值含义
1<0失败
20af和src格式不对
  • 示例
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
 
int main(int argc,char* argv[]){
    char ip[INET_ADDRSTRLEN];
    if(argc == 1){
        scanf("%s",ip);
    }else if(2 != argc){
        printf("usage:%s <ip>\n",argv[0]);
        return 1;
    }
    struct in_addr addr;
    memset(&addr,0,sizeof(addr));
    if(1 != inet_pton(AF_INET,(argc == 1?ip:argv[1]),&addr)){
        perror("inet_pton err");
        return 1;
    }
    printf("%x\n",addr.s_addr);
}

网络字节序长整型转点分十进制数串

const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt)
  • 参数
No.参数含义
1af地址族。AF_INET/AF_INET6
2src网络字节序长整型IP地址
3dst点分十进制IP地址字符串
4cnt缓存区dst的大小
  • 返回值
No.返回值含义
1NULL失败
2非NULLdst指针
  • 示例
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
 
int main(int argc,char* argv[]){
    if(2 != argc){
        printf("usage:%s <dec num>\n",argv[0]);
        return 1;
    }
    struct in_addr addr;
    memset(&addr,0,sizeof(addr));
    addr.s_addr = strtol(argv[1],NULL,16);
    char ip[INET_ADDRSTRLEN];
    if(NULL == inet_ntop(AF_INET,&addr,ip,sizeof(ip))){
        perror("inet_ntop err");
        return 1;
    }
    printf("%s\n",ip);
}

1.1.3 主机名转换操作

No.函数功能
1struct hostent *gethostbyname(const char *hostname)主机名转地址
2struct hostent *gethostbyaddr(const char * addr, int len, int type)地址转主机名
3struct hostent *gethostbyaddr(const char * addr, int len, int type)地址转主机名

1.1.3.1 主机名字和地址信息struct hostent

No.参数含义
1h_name主机名字
2h_aliases以空指针结尾的主机别名队列
3h_addrtype地址类型。AF_INET/AF_INET6
4h_length地址长度。在AF_INET类型地址中为4
5h_addr第一个IP地址
6h_addr_list以空指针结尾的IP地址的列表

1.1.3.2 主机名转地址

struct hostent *gethostbyname(const char *hostname)
  • 参数
No.参数含义
1hostname主机名
  • 返回值
No.返回值含义
1NULL出错
2非NULLhostent结构指针
  • 示例
#include <stdio.h>
#include <netdb.h>
#include <arpa/inet.h>
 
int main(int argc,char** argv){
 
    struct hostent* host = gethostbyname(argv[1]);
    if(NULL == host){
        herror("gethostbyname err");
        return 1;
    }
    printf("hostname:%s\n",host->h_name);
    printf("aliases:");
    while(*host->h_aliases != NULL){
        printf("%s ",*host->h_aliases);
        host->h_aliases++;
    }
    printf("\n");
    printf("addrtype:%s\n",host->h_addrtype == AF_INET?"AF_INET":"AF_INET6");
    printf("length:%d\n",host->h_length);
    printf("addrlist:");
    while(*host->h_addr_list != NULL){
        printf("%s ",inet_ntoa(*(struct in_addr*)*host->h_addr_list));
        host->h_addr_list++;
    }
}

1.1.3.3 地址转主机名

struct hostent *gethostbyaddr(const char * addr, int len, int type)
No.参数含义
1addr网络字节顺序地址
2len地址的长度。在AF_INET类型地址中为4
3type地址类型。AF_INET/AF_INET6
  • 返回值
No.返回值含义
1NULL出错
2非NULLhostent结构指针
  • 示例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <netdb.h>
#include <arpa/inet.h>
 
int main(int argc,char* argv[]){
    int c,af=AF_INET;
    while((c = getopt(argc,argv,"6")) != -1){
        switch(c){
            case '6':
                af = AF_INET6;
            break;
        }
    }
 
    if(optind != argc-1){
        printf("usage:%s [-6] <ip>\n",argv[0]);
        return 1;
    }
 
    struct in_addr addr;
    struct in6_addr addr6;
    void* dst = af == AF_INET ? (void*)&addr : (void*)&addr6;
 
    if(0 >= inet_pton(af,argv[1],dst)){
        perror("inet_pton error");
        return 1;
    }
    socklen_t len = AF_INET ? sizeof(struct in_addr) : sizeof(struct in6_addr) ;
    struct hostent * phost = gethostbyaddr(dst,len,af);
    if(NULL == phost){
        perror("gethostbyname error");
        return 1;
    }
     
    char ip[20];
    switch( phost->h_addrtype ){
        case AF_INET:
        case AF_INET6:
            if(NULL == inet_ntop(phost->h_addrtype,(void*)phost->h_addr,ip,20)){
                perror("inet_ntop error");
                return 1;
            }
            printf("host:%s\n0x%x = %s",argv[1],phost->h_addr,ip);
            break;
    }
}

2. socket操作

2.1 接口

2.1.1 创建

int socket(int domain, int type, int protocol)
No.参数含义
1domain协议域
2type类型
3protocol协议
  • 返回值
No.返回值含义
1-1失败
2>0socket描述符

2.1.2 关闭

int close(int sockfd)
int shutdown(int sockfd,int howto)
No.参数含义
1sockfdsocket套接字
2howto关闭方式
  • 关闭方式
No.方式含义
1SHUT_RD0关闭连接的读
2SHUT_WR1关闭连接的写
3SHUT_RDWR2连接的读和写都关闭

2.1.3 属性

设置

int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen)
No.参数含义
1sockfd套接字描述符
2level选项层次
3optname选项
4optval选项值指针
5optlenoptval缓冲区长度
  • 选项层次
No.参数含义
1SOL_SOCKET通用套接字选项
2IPPROTO_TCPTCP选项
3IPPROTO_IPIP选项
4IPPROTO_IPV6IPv6选项

选项分为SOL_SOCKET级别和IPPROTO_IP级别两个级别

  • SOL_SOCKET级别
No.参数含义
1SO_REUSEADDR让端口释放后立即就可以被再次使用。一个端口释放后会等待两分钟之后才能再被使用。
2SO_RCVBUF接收确定缓冲区大小
3SO_SNDBUF发送缓冲区大小
4SO_SNDTIMEO发送时限
5SO_RCVTIMEO接收时限
6SO_BROADCAST广播
7SO_DONTLINGER关闭端口不进入TIME_WAIT状态
8SO_LINGER关闭端口进入TIME_WAIT状态的属性
  • IPPROTO_IP级别
No.参数含义
1IP_ADD_MEMBERSHIP加入指定的组播组。
2IP_DROP_MEMBERSHIP离开指定的组播组。
3IP_MULTICAST_IF指定发送组播数据的IP地址。
4IP_MULTICAST_LOOP发送组播数据的主机是否作为接收组播数据的组播成员。
  • 返回值
No.返回值含义
10成功
2-1失败
  • 获取
int getsockopt(int sockfd, int level, int optname,void *optval, socklen_t *optlen)
No.参数含义
1sockfd套接字描述符
2level选项层次
3optname选项
4optval选项值指针
5optlenoptval缓冲区长度
  • 返回值
No.返回值含义
10成功
2-1失败

2.1.4 绑定

int bind(int socket, const struct sockaddr* address, socklen_t address_len)
No.参数含义
1socket套接字描述符
2address地址和端口号
3address_lenaddress缓冲区的长度
  • 返回值
No.返回值含义
10成功
2SOCKET_ERROR失败

2.1.5 监听

int listen(int sockfd, int backlog)
No.参数含义
1sockfd监听的socket描述符
2backlog排队的最大连接个数
  • 返回值
No.返回值含义
10成功
2-1失败

2.1.6 连接

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
No.参数含义
1sockfd客户端的socket描述字
2addr服务器的socket地址
3addrlen服务器的socket地址的长度
  • 返回值
No.返回值含义
10成功
2-1失败

2.1.7 接受

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
No.参数含义
1sockfd服务器的socket描述符,监听socket描述符
2addr客户端的socket地址
3addrlen客户端的socket地址的长度
  • 返回值
No.返回值含义
1非-1连接描述符
2-1失败
  • 说明

如果不需要获取客户端的套接字地址,后两个参数可设置为NULL。

 int connfd = accept(listenfd,NULL,NULL);

如果需要获取,则按照如下方式设置。

struct sockaddr_in remote_addr;
bzero(&remote_addr,sizeof(remote_addr));
socklen_t remote_addr_len = sizeof(remote_addr);
int connfd = accept(listenfd,(struct sockaddr*)&remote_addr,&remote_addr_len);

2.1.8 发送

ssize_t write(int fd, const void *buf, size_t len);
No.参数含义
1fd文件描述符
2buf写入数据
3len写入数据的长度
  • 返回值
No.返回值含义
1>0实际所写的字节数
2<0出错
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
No.参数含义
1sockfdsockfd文件描述符
2buf写入数据
3len写入数据的长度
4flags通常为0
  • 返回值
No.返回值含义
1>0实际所写的字节数
2<0出错
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
No.参数含义
1sockfdsockfd文件描述符
2buf写入数据
3len写入数据的长度
4flags通常为0
5dest_addr目标socket地址
6addrlen目标socket地址长度
  • 返回值
No.返回值含义
1>0实际所写的字节数
2<0出错

2.1.9 接收

ssize_t read(int fd, void *buf, size_t len);
No.参数含义
1fd文件描述符
2buf读取数据
3len读取数据的长度
  • 返回值
No.返回值含义
10读到文件的结束
2>0实际所读的字节数
3<0出错
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
No.参数含义
1fd文件描述符
2buf读取数据
3len读取数据的长度
4flags通常为0
  • 返回值
No.返回值含义
10读到文件的结束
2>0实际所读的字节数
3<0出错
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen)
No.参数含义
1fd文件描述符
2buf读取数据
3len读取数据的长度
4flags通常为0
5dest_addr目标socket地址
6addrlen目标socket地址长度
  • 返回值

No.| 返回值| 含义
1 |0 |读到文件的结束
2 |>0| 实际所读的字节数
3 |<0 |出错

3. 实践

3.1 特殊地址设置

No.特殊地址ipv4ipv6
1通配地址in_addr.sin_addr.s_addr = htonl(INADDR_ANY)in6_addr.sin6_addr=in6addr_any
2回环地址in_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK)in6_addr.sin6_addr=in6addr_loopback
3广播地址in_addr.sin_addr.s_addr = htonl(INADDR_BROADCAST)无广播

3.2 原则

  • 客户端/发送端
    必须指定连接/发送的IP(广播地址、回环地址或者某个具体地址)。
    必须指定连接/发送的port。
  • 服务器/接受端
    IP指定为通配地址、回环地址或者某个具体地址。
    必须指定绑定监听/接受的port。

netstat 查看网络连接状态、socket端口打开状态

No.选项作用
1-antp查看tcp的状态
2-anup查看udp的状态

3.3 TCP基本流程

在这里插入图片描述

No.C/S函数
1Serversocket()、bind()、listen()、accept()、recv()/read()、send()/write()
2Clientsocket()、connect()、send()/write()、recv()/read()

案例1:一个简单的TCP的客户端和服务器

  • 客户端
  1. 创建连接套接字
int connfd = socket(AF_INET,SOCK_STREAM,0); //socket括号中中(套接字地址簇类型,为AF_INET:IPv4,流式套接字,自动匹配为TCP协议)
if(-1 == connfd){
    cout << "socket error" << endl;
    return 1;
}
  1. 连接服务器
struct sockaddr_in remote_addr;
remote_addr.sin_family = AF_INET; // 协议:IPv4
remote_addr.sin_addr.s_addr = inet_addr(argv[1]); // IP地址:网络字节序长整型IP地址
remote_addr.sin_port = htons(atoi(argv[2])); // 端口号
if(-1==connect(connfd,(struct sockaddr*)&remote_addr,sizeof(remote_addr))){
    cout << "connect error" << endl;
    return 1;
}else{
    cout << "connect success" << endl;
}
  1. 发送信息
string message;
getline(cin,message);
write(connfd,message.c_str(),message.size()+1);
  1. 接收数据
char buffer[1024] = {0};
read(connfd,buffer,sizeof(buffer));
cout << buffer << endl;
  1. 关闭套接字
close(connfd);
  • 客户端完整代码
#include <iostream>
#include <arpa/inet.h>
#include <unistd.h>
using namespace std;

//  ./clinet IP port
int main(int argc,char* argv[]){
    if(3!=argc){
        cout << "Usage:" << argv[0] << " IP port" << endl;
        return 1;
    }
    // 1.创建连接套接字
    int connfd = socket(AF_INET,SOCK_STREAM,0);
    if(-1 == connfd){
        cout << "socket error" << endl;
        return 1;
    }

    // 2. 连接服务器
    struct sockaddr_in remote_addr;
    remote_addr.sin_family = AF_INET; // 协议
    remote_addr.sin_addr.s_addr = inet_addr(argv[1]); // IP地址
    remote_addr.sin_port = htons(atoi(argv[2])); // 端口号
    if(-1==connect(connfd,(struct sockaddr*)&remote_addr,sizeof(remote_addr))){
        cout << "connect error" << endl;
        return 1;
    }else{
        cout << "connect success" << endl;
    }

    // 3.发送信息
    string message;
    getline(cin,message);
    write(connfd,message.c_str(),message.size()+1);
   
    // 4.接收数据
    char buffer[1024] = {0};
    read(connfd,buffer,sizeof(buffer));
    cout << buffer << endl;

    // 5. 关闭套接字
    close(connfd);

    return 0;
}
  • 服务端

  • 服务端完整代码

#include <iostream>
#include <arpa/inet.h>
#include <unistd.h>
#include <strings.h>
using namespace std;

// ./server ip port
int main(int argc,char* argv[]){
    if(3!=argc){
        cout << "Usage:" << argv[0] << " IP port" << endl;
        return 1;
    }
    // 1. 监听套接字
    int listenfd = socket(AF_INET,SOCK_STREAM,0);
    if(-1 == listenfd){
        cout << "listen socket error" << endl;
        return 1;
    }

    // 2. 绑定
    struct sockaddr_in local_addr;
    local_addr.sin_family = AF_INET; // 协议
    local_addr.sin_addr.s_addr = inet_addr(argv[1]); // IP地址
    local_addr.sin_port = htons(atoi(argv[2])); // 端口号
    
    if(-1 == bind(listenfd,(struct sockaddr*)&local_addr,sizeof(local_addr))){
        cout << "bind error" << endl;
        return 1;
    }else{
        cout << "bind success" << endl;
    }
    // 3. 监听设置
    if(-1==listen(listenfd,10)){
        cout << "listen error" << endl;
        return 1;
    }else{
        cout << "listen success" << endl;
    }

    // 4. 等待接收
    struct sockaddr_in remote_addr;
    bzero(&remote_addr,sizeof(remote_addr));
    socklen_t remote_addr_len = sizeof(remote_addr);
    accept(listenfd,(struct sockaddr*)&remote_addr,&remote_addr_len); //显示连接的客户端IP
	// int connfd = accept(listenfd,NULL,NULL);

    if(-1 == connfd){
        cout << "accept error" << endl;
        return 1;
    }else{
        cout << "accept success" << endl;
        cout << inet_ntoa(remote_addr.sin_addr) << ":" << ntohs(remote_addr.sin_port) << endl;
//	将连接成功的客户端ip打印出来
    }

    // 5. 读数据
    char buffer[1024] = {0};
    read(connfd,buffer,sizeof(buffer));
    cout << buffer << endl;

    // 6. 写数据
    string message;
    getline(cin,message);
    write(connfd,message.c_str(),message.size()+1);

    // 7. 关闭套接字
    close(connfd);
    close(listenfd);
}
  • 说明
    使用下面的代码,是为了避免出现Bind error: Address already in use
if(-1 == bind(listenfd,(struct sockaddr*)&local_addr,sizeof(local_addr)))

启用SO_REUSEADDR选项后,bind()函数将允许地址的立即重用。

  • Socket与三次握手
    在这里插入图片描述

  • Socket与四次挥手
    在这里插入图片描述

  • 测试是否成功

在服务端(需要先打开服务端)

./server 127.0.0.1 8088

在客户端

 ./client 127.0.0.1 8088
  • 效果展示
    在这里插入图片描述
  • 说明:关于IP连接问题
    在这里插入图片描述

案例2:简单的TCP的客户端和服务器(接收数据和发送数据采用多线程方式)

  • 客户端完整代码
#include <iostream>
#include <arpa/inet.h>
#include <unistd.h>
#include <strings.h>
using namespace std;

void show_connect(int fd) {
    // 获取本地地址和端口
    struct sockaddr_in local_addr;
    socklen_t local_addr_len = sizeof(local_addr);
    bzero(&local_addr,local_addr_len);
    getsockname(fd,(struct sockaddr*)&local_addr,&local_addr_len);
    cout << "local " << inet_ntoa(local_addr.sin_addr) << ":" << ntohs(local_addr.sin_port) << endl;

    // 获取远程地址和端口
    struct sockaddr_in remote_addr;
    socklen_t remote_addr_len = sizeof(remote_addr);
    bzero(&remote_addr,remote_addr_len);
    getpeername(fd,(struct sockaddr*)&remote_addr,&remote_addr_len);
    cout << "remote " << inet_ntoa(remote_addr.sin_addr) << ":" << ntohs(remote_addr.sin_port) << endl;
}


void* read_msg(void* fd) {
    int connfd = *(int*)fd;
    while(true) {
        // 4.接收数据
        char buffer[1024] = {0};
        int len = read(connfd,buffer,sizeof(buffer));
        if(0==len) {
            cout << "server exit" << endl;
            break;
        } else {
            cout <<"server:"<< buffer << endl;
        }
    }

}
void* write_msg(void* fd) {
    int connfd = *(int*)fd;
    while(true) {
        // 3.发送信息
        string message;
        getline(cin,message);
        write(connfd,message.c_str(),message.size()+1);
    }

}

//  ./clinet IP port
int main(int argc,char* argv[]) {
    if(3!=argc) {
        cout << "Usage:" << argv[0] << " IP port" << endl;
        return 1;
    }
    // 1.创建连接套接字
    int connfd = socket(AF_INET,SOCK_STREAM,0);
    if(-1 == connfd) {
        cout << "socket error" << endl;
        return 1;
    }

    // 2. 连接服务器
    struct sockaddr_in remote_addr;
    remote_addr.sin_family = AF_INET; // 协议
    remote_addr.sin_addr.s_addr = inet_addr(argv[1]); // IP地址
    remote_addr.sin_port = htons(atoi(argv[2])); // 端口号
    if(-1==connect(connfd,(struct sockaddr*)&remote_addr,sizeof(remote_addr))) {
        cout << "connect error" << endl;
        return 1;
    } else {
        cout << "connect success" << endl;
        show_connect(connfd);
    }

    pthread_t tids[2];
    pthread_create(tids,NULL,write_msg,&connfd);
    pthread_create(tids+1,NULL,read_msg,&connfd);

    for(auto tid:tids){
        pthread_join(tid,NULL);
    }
    // 5. 关闭套接字
    close(connfd);

    return 0;
}
  • 服务端完整代码
#include <iostream>
#include <arpa/inet.h>
#include <unistd.h>
#include <strings.h>
using namespace std;


void show_connect(int fd) {
    struct sockaddr_in local_addr;
    socklen_t local_addr_len = sizeof(local_addr);
    bzero(&local_addr,local_addr_len);
    getsockname(fd,(struct sockaddr*)&local_addr,&local_addr_len);
    cout << "local " << inet_ntoa(local_addr.sin_addr) << ":" << ntohs(local_addr.sin_port) << endl;

    struct sockaddr_in remote_addr;
    socklen_t remote_addr_len = sizeof(remote_addr);
    bzero(&remote_addr,remote_addr_len);
    getpeername(fd,(struct sockaddr*)&remote_addr,&remote_addr_len);
    cout << "remote " << inet_ntoa(remote_addr.sin_addr) << ":" << ntohs(remote_addr.sin_port) << endl;
}

void* read_msg(void* fd) {
    int connfd = *(int*)fd;
    while(true) {
        // 5. 读数据
        char buffer[1024] = {0};
        int len = recv(connfd,buffer,sizeof(buffer),0);
        if(0==len) {
            cout << "client exit" << endl;
            break;
        } else {
            cout << "client:" << buffer << endl;
        }
    }
}
void* write_msg(void* fd) {
    int connfd = *(int*)fd;
    while(true) {
        // 6. 写数据
        string message;
        getline(cin,message);
        send(connfd,message.c_str(),message.size()+1,0);
    }

}
// ./server ip port
int main(int argc,char* argv[]) {
    if(3!=argc) {
        cout << "Usage:" << argv[0] << " IP port" << endl;
        return 1;
    }
    // 1. 监听套接字
    int listenfd = socket(AF_INET,SOCK_STREAM,0);
    if(-1 == listenfd) {
        cout << "listen socket error" << endl;
        return 1;
    }

    // 2. 绑定
    struct sockaddr_in local_addr;
    local_addr.sin_family = AF_INET; // 协议
    local_addr.sin_addr.s_addr = inet_addr(argv[1]); // IP地址
    local_addr.sin_port = htons(atoi(argv[2])); // 端口号

    if(-1 == bind(listenfd,(struct sockaddr*)&local_addr,sizeof(local_addr))) {
        cout << "bind error" << endl;
        return 1;
    } else {
        cout << "bind success" << endl;
    }
    // 3. 监听设置
    if(-1==listen(listenfd,10)) {
        cout << "listen error" << endl;
        return 1;
    } else {
        cout << "listen success" << endl;
    }

    // 4. 等待接收
    struct sockaddr_in remote_addr;
    bzero(&remote_addr,sizeof(remote_addr));
    socklen_t remote_addr_len = sizeof(remote_addr);
    int connfd = accept(listenfd,(struct sockaddr*)&remote_addr,&remote_addr_len);
    //int connfd = accept(listenfd,NULL,NULL);
    if(-1 == connfd) {
        cout << "accept error" << endl;
        return 1;
    } else {
        cout << "accept success" << endl;
        cout << inet_ntoa(remote_addr.sin_addr) <<":" << ntohs(remote_addr.sin_port) << endl;
        show_connect(connfd);
    }

    pthread_t tids[2];
    pthread_create(tids,NULL,write_msg,&connfd);
    pthread_create(tids+1,NULL,read_msg,&connfd);

    for(auto tid:tids){
        pthread_join(tid,NULL);
    }
    // 7. 关闭套接字
    close(connfd);
    close(listenfd);
}

案例3:简单的TCP的客户端和服务器(一个服务器支持多个客户端)

  • 服务端完整代码
#include <iostream>
#include <vector>
#include <thread>
#include <arpa/inet.h>
#include <unistd.h>
#include <strings.h>
using namespace std;


void show_connect(int fd) {
    struct sockaddr_in local_addr;
    socklen_t local_addr_len = sizeof(local_addr);
    bzero(&local_addr,local_addr_len);
    getsockname(fd,(struct sockaddr*)&local_addr,&local_addr_len);
    cout << "local " << inet_ntoa(local_addr.sin_addr) << ":" << ntohs(local_addr.sin_port) << endl;

    struct sockaddr_in remote_addr;
    socklen_t remote_addr_len = sizeof(remote_addr);
    bzero(&remote_addr,remote_addr_len);
    getpeername(fd,(struct sockaddr*)&remote_addr,&remote_addr_len);
    cout << "remote " << inet_ntoa(remote_addr.sin_addr) << ":" << ntohs(remote_addr.sin_port) << endl;
}
// ./server ip port
int main(int argc,char* argv[]) {
    if(3!=argc) {
        cout << "Usage:" << argv[0] << " IP port" << endl;
        return 1;
    }

    // 1. 监听套接字
    int listenfd = socket(AF_INET,SOCK_STREAM,0);
    if(-1 == listenfd) {
        cout << "listen socket error" << endl;
        return 1;
    }

    // 设置端口重复利用
    int flag = 1;
    setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&flag,sizeof(flag));

    // 2. 绑定
    struct sockaddr_in local_addr;
    local_addr.sin_family = AF_INET; // 协议
    local_addr.sin_addr.s_addr = inet_addr(argv[1]); // IP地址
    local_addr.sin_port = htons(atoi(argv[2])); // 端口号

    if(-1 == bind(listenfd,(struct sockaddr*)&local_addr,sizeof(local_addr))) {
        cout << "bind error" << endl;
        return 1;
    } else {
        cout << "bind success" << endl;
    }
    // 3. 监听设置
    if(-1==listen(listenfd,10)) {
        cout << "listen error" << endl;
        return 1;
    } else {
        cout << "listen success" << endl;
    }

    vector<int> fds;
    thread([&fds]{ // 服务器广告线程
        while(true){
            string message;
            getline(cin,message);
            message = "广告:" + message;
            for(auto fd:fds){
               write(fd,message.c_str(),message.size()+1);
            }
        }
                            
    }).detach();


    // 4. 等待接收
    while(true) {
        struct sockaddr_in remote_addr;
        bzero(&remote_addr,sizeof(remote_addr));
        socklen_t remote_addr_len = sizeof(remote_addr);
        int connfd = accept(listenfd,(struct sockaddr*)&remote_addr,&remote_addr_len);
        //int connfd = accept(listenfd,NULL,NULL);
        if(-1 == connfd) {
            cout << "accept error" << endl;
            return 1;
        } else {
            cout << "accept success" << endl;
            cout << inet_ntoa(remote_addr.sin_addr) <<":" << ntohs(remote_addr.sin_port) << endl;
            show_connect(connfd);
            fds.push_back(connfd);
        }
        thread([connfd,&fds]{
            while(true){
                char buffer[1024] = {0};
                int n = read(connfd,buffer,1024);// 读取客户端发过来的信息
                if(n == 0){
                    break;
                }else{
                    for(auto fd:fds){ // 发送信息给所有的客户端
                        if(fd==connfd) continue;
                        write(fd,buffer,1024);
                    }
                }
            }                                        
        }).detach();
    }
    close(listenfd);
}

-注意

// 设置端口重复利用
int flag = 1;
setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&flag,sizeof(flag));

  • 客户端完整代码
#include <iostream>
#include <arpa/inet.h>
#include <unistd.h>
#include <strings.h>
using namespace std;

string name;

void show_connect(int fd) {
    // 获取本地地址和端口
    struct sockaddr_in local_addr;
    socklen_t local_addr_len = sizeof(local_addr);
    bzero(&local_addr,local_addr_len);
    getsockname(fd,(struct sockaddr*)&local_addr,&local_addr_len);
    cout << "local " << inet_ntoa(local_addr.sin_addr) << ":" << ntohs(local_addr.sin_port) << endl;

    // 获取远程地址和端口
    struct sockaddr_in remote_addr;
    socklen_t remote_addr_len = sizeof(remote_addr);
    bzero(&remote_addr,remote_addr_len);
    getpeername(fd,(struct sockaddr*)&remote_addr,&remote_addr_len);
    cout << "remote " << inet_ntoa(remote_addr.sin_addr) << ":" << ntohs(remote_addr.sin_port) << endl;
}


void* read_msg(void* fd) {
    int connfd = *(int*)fd;
    while(true) {
        // 4.接收数据
        char buffer[1024] = {0};
        int len = read(connfd,buffer,sizeof(buffer));
        if(0==len) {
            cout << "server exit" << endl;
            break;
        } else {
            cout << buffer << endl;
        }
    }

}
void* write_msg(void* fd) {
    int connfd = *(int*)fd;
    while(true) {
        // 3.发送信息
        string message;
        getline(cin,message);
        message = name + ":" + message;
        write(connfd,message.c_str(),message.size()+1);
    }

}

//  ./clinet IP port name
int main(int argc,char* argv[]) {
    if(4!=argc) {
        cout << "Usage:" << argv[0] << " IP port name" << endl;
        return 1;
    }
    name = argv[3];
    // 1.创建连接套接字
    int connfd = socket(AF_INET,SOCK_STREAM,0);
    if(-1 == connfd) {
        cout << "socket error" << endl;
        return 1;
    }

    // 2. 连接服务器
    struct sockaddr_in remote_addr;
    remote_addr.sin_family = AF_INET; // 协议
    remote_addr.sin_addr.s_addr = inet_addr(argv[1]); // IP地址
    remote_addr.sin_port = htons(atoi(argv[2])); // 端口号
    if(-1==connect(connfd,(struct sockaddr*)&remote_addr,sizeof(remote_addr))) {
        cout << "connect error" << endl;
        return 1;
    } else {
        cout << "connect success" << endl;
        show_connect(connfd);
    }

    pthread_t tids[2];
    pthread_create(tids,NULL,write_msg,&connfd);
    pthread_create(tids+1,NULL,read_msg,&connfd);

    for(auto tid:tids){
        pthread_join(tid,NULL);
    }
    // 5. 关闭套接字
    close(connfd);

    return 0;
}

3.4 UDP

在这里插入图片描述

3.4.1 单播

基本流程

  • 发送者
  1. 打开socket
int connfd = socket(AF_INET,SOCK_DGRAM,0);
if(-1 == connfd) {
    cout << "create socket error" << endl;
    return 1;
}
  1. 设置发送地址和端口
struct sockaddr_in local_addr;
local_addr.sin_family = AF_INET;// 套接字地址簇,一般使用AF_INET
local_addr.sin_addr.s_addr = inet_addr(argv[1]); // IP地址,网络序
local_addr.sin_port = htons(atoi(argv[2]));// 16位端口,网络序
  1. 发送数据
    发送数据和接收数据采用不同的线程
    thread([=] {
        for(;;) {
            // 3.发送数据
            string message;
            getline(cin,message);

            sendto(connfd,message.c_str(),message.size()+1,0,(struct sockaddr*)&local_addr,sizeof(local_addr));

        }
    }).detach();
    for(;;) {
        // 接收数据
        char buffer[1024];
        socklen_t len = sizeof(local_addr);
        recvfrom(connfd,buffer,1024,0,(struct sockaddr*)&local_addr,&len);
        cout << buffer << endl;
    }
  1. 关闭socket
 close(connfd);
  • 完整案例
#include <iostream>
#include <thread>
#include <arpa/inet.h> // socket
#include <unistd.h>
using namespace std;

// ./unicast_send IP Port
int main(int argc,char* argv[]) {
    if(3!=argc) {
        cout << "Usage:" << argv[0] << " IP Port" << endl;
        return 1;
    }

    // 1.创建套接字
    int connfd = socket(AF_INET,SOCK_DGRAM,0);
    if(-1 == connfd) {
        cout << "create socket error" << endl;
        return 1;
    }

    // 2.绑定端口和地址
    struct sockaddr_in local_addr;
    local_addr.sin_family = AF_INET;
    local_addr.sin_addr.s_addr = inet_addr(argv[1]);
    local_addr.sin_port = htons(atoi(argv[2]));

    thread([=] {
        for(;;) {
            // 3.发送数据
            string message;
            getline(cin,message);

            sendto(connfd,message.c_str(),message.size()+1,0,(struct sockaddr*)&local_addr,sizeof(local_addr));

        }
    }).detach();
    for(;;) {
        // 接收数据
        char buffer[1024];
        socklen_t len = sizeof(local_addr);
        recvfrom(connfd,buffer,1024,0,(struct sockaddr*)&local_addr,&len);
        cout << buffer << endl;
    }

    // 4.关闭套接字
    close(connfd);

}
  • 接收者
  1. 打开socket
int connfd = socket(AF_INET,SOCK_DGRAM,0);
if(-1 == connfd) {
    cout << "create socket error" << endl;
    return 1;
}
  1. 设置接收地址和端口
 struct sockaddr_in local_addr;
    local_addr.sin_family = AF_INET; // 套接字地址簇,一般使用AF_INET
    local_addr.sin_addr.s_addr = inet_addr(argv[1]); 
    local_addr.sin_port = htons(atoi(argv[2]));// 16位端口,网络序
  1. 端口绑定
if(-1 == bind(connfd,(struct sockaddr*)&local_addr,sizeof(local_addr))) {
        cout << "bind error" << endl;
        return 1;
    }
  1. 接受和发送数据
  struct sockaddr_in remote_addr;
    bzero(&remote_addr,sizeof(remote_addr));
    socklen_t len;
    thread([&] {
        for(;;) {
            // 接收数据
            char buffer[1024];
            recvfrom(connfd,buffer,1024,0,(struct sockaddr*)&remote_addr,&len);
            cout << buffer << endl;
        }
    }).detach();
    for(;;) {

        // 发送数据
        string message;
        getline(cin,message);
        sendto(connfd,message.c_str(),message.size()+1,0,(struct sockaddr*)&remote_addr,len);
    }
  1. 关闭socket
close(connfd); 
  • 完整案例
#include <iostream>
#include <thread>
#include <arpa/inet.h> // socket
#include <unistd.h>
#include <strings.h>
using namespace std;

// ./unicast_recv IP Port
int main(int argc,char* argv[]) {
    if(3!=argc) {
        cout << "Usage:" << argv[0] << " IP Port" << endl;
        return 1;
    }

    // 1.创建套接字
    int connfd = socket(AF_INET,SOCK_DGRAM,0);
    if(-1 == connfd) {
        cout << "create socket error" << endl;
        return 1;
    }

    // 2.绑定端口和地址
    struct sockaddr_in local_addr;
    local_addr.sin_family = AF_INET;
    local_addr.sin_addr.s_addr = inet_addr(argv[1]);
    local_addr.sin_port = htons(atoi(argv[2]));
    if(-1 == bind(connfd,(struct sockaddr*)&local_addr,sizeof(local_addr))) {
        cout << "bind error" << endl;
        return 1;
    }
    struct sockaddr_in remote_addr;
    bzero(&remote_addr,sizeof(remote_addr));
    socklen_t len;
    thread([&] {
        for(;;) {
            // 接收数据
            char buffer[1024];
            recvfrom(connfd,buffer,1024,0,(struct sockaddr*)&remote_addr,&len);
            cout << buffer << endl;
        }
    }).detach();
    for(;;) {

        // 发送数据
        string message;
        getline(cin,message);
        sendto(connfd,message.c_str(),message.size()+1,0,(struct sockaddr*)&remote_addr,len);
    }

    close(connfd);

}
  • 测试

按照下表设置服务器。

单播接受端

No.RecvIPport
1unicast_recv0.0.0.08001
2unicast_recv127.0.0.18001
3unicast_recvXXX.XXX.XXX.XXX8001

单播播发送端

unicast_send 0.0.0.0 8001 HelloWorld
unicast_send 127.0.0.1 HelloWorld
unicast_send XXX.XXX.XXX.XXX 8001 HelloWorld

3.4.2 组播/多播

基本流程

  • 发送者

与单播发送者一致

  • 接收者
  1. 打开套接字
int connfd = socket(AF_INET,SOCK_DGRAM,0);
if(-1 == connfd) {
    cout << "create socket error" << endl;
    return 1;
}
  1. 构建服务器地址结构
struct sockaddr_in local_addr;
local_addr.sin_family = AF_INET;
local_addr.sin_addr.s_addr = inet_addr(argv[1]);
local_addr.sin_port = htons(atoi(argv[2]));
  1. 绑定地址
 if(-1 == setsockopt(connfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&group,sizeof(group))){
        cout << "multicast setting error" << endl;
        return 1;
    }
4. 构建组播属性结构
 struct ip_mreq group;
    group.imr_interface.s_addr = inet_addr(argv[1]);
    group.imr_multiaddr.s_addr = inet_addr(argv[3]);
  1. 设置组播权限和属性
if(-1 == setsockopt(connfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&group,sizeof(group))){
    cout << "multicast setting error" << endl;
    return 1;
}
  1. 设置客户端组播地址
struct sockaddr_in remote_addr;
bzero(&remote_addr,sizeof(remote_addr));
  1. 发送和接收数据
 socklen_t len;
    thread([&] {
        for(;;) {
            // 接收数据
            char buffer[1024];
            recvfrom(connfd,buffer,1024,0,(struct sockaddr*)&remote_addr,&len);
            cout << buffer << endl;
        }
    }).detach();
    for(;;) {

        // 发送数据
        string message;
        getline(cin,message);
        sendto(connfd,message.c_str(),message.size()+1,0,(struct sockaddr*)&remote_addr,len);
    }
  1. 关闭套接字
 close(connfd);
  • 完整案例
#include <iostream>
#include <thread>
#include <arpa/inet.h> // socket
#include <unistd.h>
#include <strings.h>
using namespace std;

// ./multicast_recv IP Port multiIP
int main(int argc,char* argv[]) {
    if(4!=argc) {
        cout << "Usage:" << argv[0] << " IP Port multiIP" << endl;
        return 1;
    }

    // 1.创建套接字
    int connfd = socket(AF_INET,SOCK_DGRAM,0);
    if(-1 == connfd) {
        cout << "create socket error" << endl;
        return 1;
    }

    // 2.绑定端口和地址
    struct sockaddr_in local_addr;
    local_addr.sin_family = AF_INET;
    local_addr.sin_addr.s_addr = inet_addr(argv[1]);
    local_addr.sin_port = htons(atoi(argv[2]));
    if(-1 == bind(connfd,(struct sockaddr*)&local_addr,sizeof(local_addr))) {
        cout << "bind error" << endl;
        return 1;
    }

    // 设置组播地址
    struct ip_mreq group;
    group.imr_interface.s_addr = inet_addr(argv[1]);
    group.imr_multiaddr.s_addr = inet_addr(argv[3]);
    if(-1 == setsockopt(connfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&group,sizeof(group))){
        cout << "multicast setting error" << endl;
        return 1;
    }

    struct sockaddr_in remote_addr;
    bzero(&remote_addr,sizeof(remote_addr));
    socklen_t len;
    thread([&] {
        for(;;) {
            // 接收数据
            char buffer[1024];
            recvfrom(connfd,buffer,1024,0,(struct sockaddr*)&remote_addr,&len);
            cout << buffer << endl;
        }
    }).detach();
    for(;;) {

        // 发送数据
        string message;
        getline(cin,message);
        sendto(connfd,message.c_str(),message.size()+1,0,(struct sockaddr*)&remote_addr,len);
    }

    close(connfd);

}
  • 测试

按照下表设置服务器。

多播接受端

No.RecvIPMultiport
1multicast_recv0.0.0.0224.0.1.18002
2multicast_recv127.0.0.1224.0.1.18002
3multicast_recvXXX.XXX.XXX.XXX224.0.1.18002

单播播发送端

unicast_send 224.0.1.1 8002 HelloWorld
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值