Linux应用学习_网络开发

1. TCP套接字编程

1.1 套接字

1.1.1 通用套接字sockaddr
struct sockaddr {
    sa_family_t sa_family;  	/* address family, AF_xxx   */
    char        sa_data[14];    /* 14 bytes of protocol address */
};

typedef unsigned short sa_family_t
  • sockaddr共占用16+14=30个字节
  • 但是使用sockaddr结构体不方便进行设置,因此一般采用struct sockaddr_in
1.1.2 常用套接字 sockaddr_in
struct sockaddr_in {
  __kernel_sa_family_t  sin_family; /* Address family       */
  __be16        sin_port;   		/* Port number          */
  struct in_addr    sin_addr;   	/* Internet address     */

  /* Pad to size of `struct sockaddr'. */
  unsigned char     __pad[__SOCK_SIZE__ - sizeof(short int) -
            sizeof(unsigned short int) - sizeof(struct in_addr)];
};
struct in_addr{			/*IP地址结构*/
	u32 s_addr;			/*32位IP地址,网络字节序*/
}
1.1.3 两个套接字的对应关系
  • 两个套接字都是32个字节,大小一样,因此在使用过程中可以通过设置sockaddr_in然后强制类型转换为sockaddr
  • Linux应用学习_网络开发-LMLPHP

2. TCP——服务器

2.1 程序设计流程

  1. 套接字初始化socket
  2. 套接字与端口绑定bind
  3. 设置服务器的侦听连接listen
  4. 接受客户端连接accept
  5. 接受数据read
  6. 发送数据write
  7. 关闭套接字close
2.1.1 socket
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int socket(int domain, int type, int protocol);
  • domain用于设置网络通信的域
    • PF_UNIX,PF_LOCAL本地通信
    • PF_INETIPv4Internet协议(有时用的AF_INET,两个值一样)
    • PF_INET6IPv6Internet协议
  • tpye设置套接字通信的类型
    • SOCK_STREAM TCP连接,提供序列化的、可靠的、双向连接的字节流
    • SOCK_DGRAM UDP连接
  • protocol 用于指定某个协议的特定类型
    • 通常协议中只有一个特定类型,这样只能设置为0
  • 返回值为一个new socket

常用形式:

int sockfd;

sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
return sockfd;
}
2.1.2 bind
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
  • sockfd指向socket()函数创建的文件描述符
  • sockaddr *addr 指向一个结构体,包含地址、端口和IP地址信息
  • addrlen 结构体的长度,一般设置为sizeof(struct sockaddr)
  • 返回值为0表示绑定成功,-1表示绑定失败

常用形式:

int ret;
struct sockaddr_in my_addr;

bzero(&my_addr, sizeof(my_addr));
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(8888);
my_addr.sin_addr.s_addr = inet_addr("192.168.9.99");
ret = bind(sockfd, (struct sockaddr *)(&my_addr), sizeof(struct sockaddr ));
if (ret < 0)
{
	return ret;
}
  • htons将端口地址进行字节序转换
  • inet_addr()将字符串转换为二进制网络字节序的IP地址值
2.1.3 listen

​ 函数listen()用来初始化服务器可连接队列,服务器处理客户端连接请求的时候是顺序处理的,同一时间只能处理一个客户端连接。当多个客户端的连接请求同时到来的时候,服务器并不是同时处理,而是将不能处理的客户端连接请求放到等待队列中,这个队列的长度有listen()函数来定义。

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int listen(int sockfd, int backlog);
  • sockfd指向socket()函数创建的文件描述符
  • backlog可连接数量
  • listen()函数仅对类型为SOCK_STREAMSOCK_SEQPACkET 的协议有效
  • 运行成功返回0,失败返回-1

常用形式:

ret = listen(sockfd, 20);
if (ret < 0)
{
	return ret;	
}
2.1.4 accept

​ 通过accept()函数可以得到成功连接的客户端的IP地址、端口和协议族等信息。

accept()函数的返回值是新连接的客户端套接字文件描述符,与客户端之间的通信是通过accept()函数返回的新套接字文件描述符来进行的,而不是通过建立套接字时的文件描述符,这是程序设计的时候需要注意的地方。

 #include <sys/types.h>          /* See NOTES */
 #include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  • 返回值,当成功返回时,返回的是新连接的客户端的文件描述符,错误返回-1
  • addr 新连接的客户端的相关信息,包括IP地址、端口和协议族信息
  • addrlen 通常设置为sizeof(struct addr)

常用形式:

int sockfd;

sockfd = Server_Init();

struct sockaddr_in client_info;
int client_info_len = sizeof(struct sockaddr_in);
while (1)
{
    int client_fd = accept(sockfd , &client_info, &client_info_len);
    if (client_fd == -1)
    {
    perror("accept error\r\n");
    exit(EXIT_FAILURE);
    }

    printf("clinet_ip:%s\r\n",inet_ntoa(client_info.sin_addr));
    printf("client_port:%d\r\n",ntohs(client_info.sin_port));

}
  • inet_ntoa
  • ntohs
2.1.5 read
  • read()函数可以从套接字描述符中读取数据
int size;
char data[1024];
size = read(fd, data, 1024)
2.1.6 write
  • 当服务器在接收到一个客户端的连接后,可以通过套接字描述符进行数据的写入操作
  • 写入形式和过程与普通文件的操作方式一致
int size;
char data[1024];
size = write(fd, data, 1024);
2.1.7 close
  • close()可以关闭已经打开的socket
  • 可以通过 shutdown() 可以使用更多方式来关闭连接,允许单方面切断通信或者切断双方的通信
#include <sys/socket.h>

int shutdown(int sockfd, int how);
  • sockfd 要关闭的套接字
  • how 表示切断方式
    • SHUT_RD 切断读,之后不能使用此文件描述符进行读操作
    • SHUT_WR 切断写,之后不能使用此文件描述符进行写操作
    • SHUT_RDWR 切断读写

3. TCP——客户端

3.1 程序设计流程

  1. 套接字初始化函数socket()
  2. 客户端连接服务器connect()
  3. 接受数据read()
  4. 发送数据write()
  5. 套接字关闭close()
3.1.1 socket

同服务器socket

3.1.2 connetc

​ 客户端在建立套接字之后,不需要进行绑定地址就可以通过 connect 直接连接服务器,此函数连接指定参数的服务器,例如IP地址、端口等

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
  • sockfd 创建的套接字
  • addr 客户端需要连接的服务器的目的端口和IP地址
  • addrlen 通常设置为sizeof(struct sockaddr)
  • 连接成功时返回0,失败时返回-1

常用形式:

int sockfd;
int ret;
struct sockaddr_in server_info;

sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
    perror("sockfd failed\r\n");
    return sockfd;
}
bzero(&server_info,sizeof(struct sockaddr_in));
server_info.sin_family = AF_INET;
server_info.sin_port = htons(port);
server_info.sin_addr.s_addr = htonl(ip);

ret = connect(sockfd, &server_info, sizeof(struct sockaddr_in));
if (ret < 0)
{
    perror("connetc failed\r\n");
    return ret;
}
3.1.3 read

同服务器read

3.1.4 write

同服务器write

3.1.5 close

同服务器关闭

4. TCP 服务器客户端编程注意事项

4.1 SIGPIPE

​ 如果正在写入套接字的时候,当读取端已经关闭时,可以得到一个SIGPIPE信号。信号会终止当前进程,因为信号系统在调用系统默认处理方式之前会调用用户注册的函数,所以可以通过注册SIGPIPE信号的处理函数来获取这个信号,并进行相应的处理。例如,当服务器已经关闭,而客户端试图向套接字写入数据的时候会产生一个SIGPIPE信号,此时会稻城程序的非正常退出。

#include <signal.h>

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

常用形式:

void sigpipe_handler(int sign)
{
	printf("catch a pipe signal");
	/*free*/
}
signal(SIGPIPE, sigpipe_handler);

的时候会产生一个SIGPIPE信号,此时会稻城程序的非正常退出。

#include <signal.h>

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

常用形式:

void sigpipe_handler(int sign)
{
	printf("catch a pipe signal");
	/*free*/
}
signal(SIGPIPE, sigpipe_handler);
03-24 23:35