Linux网络编程基础-03_TCP/IP网络编程

网络编程相关API

  • socket() 创建套接字
  • bind() 绑定本机地址和端口
  • connect() 建立连接
  • listen() 设置监听套接字,把主动套接字变成动套接字
  • accept() 接受TCP连接 ,阻塞等待客户端连接请求
  • recv(), read(), recvfrom() 数据接收
  • send(), write(),sendto() 数据发送
  • close(), shutdown() 关闭套接字

socket

Linux网络编程基础-03_TCP/IP网络编程
int socket (int domain, int type, int protocol);

  • domain 是地址族
    PF_INET // internet 协议
    PF_UNIX // unix internal协议
    PF_NS // Xerox NS协议
    PF_IMPLINK // Interface Message协议

  • type // 套接字类型
    SOCK_STREAM // 流式套接字
    SOCK_DGRAM // 数据报套接字
    SOCK_RAW // 原始套接字

  • protocol 参数通常置为0

地址相关的数据结构
  • 通用地址结构

    struct sockaddr
    {    
        u_short  sa_family;    // 地址族, AF_xxx
        char  sa_data[14];     // 14字节协议地址
    };
    
  • Internet协议地址结构

    struct sockaddr_in
    {           
       u_short sin_family;      // 地址族, AF_INET,2 bytes
       u_short sin_port;      // 端口,2 bytes
       struct in_addr sin_addr;  // IPV4地址,4 bytes 	
       char sin_zero[8];        // 8 bytes unused,作为填充
    }; 
    
  • IPv4地址结构
    // internet address

    struct in_addr
    {
           in_addr_t  s_addr;            // u32 network address 
    };
    

bind

Linux网络编程基础-03_TCP/IP网络编程
int bind (int sockfd, struct sockaddr* addr, int addrLen);

  • sockfd 由socket() 调用返回

  • addr 是指向 sockaddr_in 结构的指针,包含本机IP 地址和端口号
    struct sockaddr_in
    u_short sin_family // protocol family
    u_short sin_port // port number
    struct in_addr sin_addr //IP address (32-bits)

  • addrLen : sizeof (struct sockaddr_in)

地址结构的一般用法
  1. 定义一个struct sockaddr_in类型的变量并清空 struct

    sockaddr_in myaddr;
    memset(&myaddr, 0, sizeof(myaddr));
    
  2. 填充地址信息

myaddr.sin_family = PF_INET; 			
myaddr.sin_port = htons(8888); 
myaddr.sin_addr.s_addr = inet_addr(“192.168.1.100”);
  1. 将该变量强制转换为struct sockaddr类型在函数中使用
bind(listenfd, (struct sockaddr*)(&myaddr), sizeof(myaddr));
地址转换函数

unsigned long inet_addr(char *address);

  • address是以’\0’结尾的点分IPv4字符串。该函数返回32位的地址。如果字符串包含的不是合法的IP地址,则函数返回-1。例如:
    struct in_addr addr;
    addr.s_addr = inet_addr(" 192.168.1.100 ");

char* inet_ntoa(struct in_addr address);

  • address是IPv4地址结构,函数返回一指向包含点分IP地址的静态存储区字符指针。如果错误则函数返回NULL

listen

int listen (int sockfd, int backlog);
  • sockfd:监听连接的套接字
  • backlog
    指定了正在等待连接的最大队列长度,它的作用在于处理可能同时出现的几个连接请求。
    DoS(拒绝服务)攻击即利用了这个原理,非法的连接占用了全部的连接数,造成正常的连接请求被拒绝。 一般填5, 测试得知,ARM最大为8
  • 返回值: 0 或 -1
  • 内核中服务器的套接字fd会维护2个链表:
  1. 正在三次握手的的客户端链表(数量=2*backlog+1)
  2. 已经建立好连接的客户端链表(已经完成3次握手分配好了newfd)
  3. 比如:listen(fd, 5); //表示系统允许11(=2*5+1)个客户端同时进行三次握手

accept()

Linux网络编程基础-03_TCP/IP网络编程
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) ;

  • 返回值:已建立好连接的套接字或-1
  • 头文件
    #include <sys/types.h>
    #include <sys/socket.h>
  • sockfd : 监听套接字
  • addr : 对方地址
  • addrlen:地址长度

connect()

Linux网络编程基础-03_TCP/IP网络编程

int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
  • 返回值:0 或 -1
  • 头文件:
    #include <sys/types.h>
    #include <sys/socket.h>
  • sockfd : socket返回的文件描述符
  • serv_addr : 服务器端的地址信息
  • addrlen : serv_addr的长度

send()

Linux网络编程基础-03_TCP/IP网络编程

ssize_t  send(int  socket,  const  void  *buffer,  size_t  length, int flags);
  • 返回值:
    成功:实际发送的字节数
    失败:-1, 并设置errno
  • 头文件:
    #include <sys/socket.h>
  • buffer : 发送缓冲区首地址
  • length : 发送的字节数
  • flags : 发送方式(通常为0)

recv()

Linux网络编程基础-03_TCP/IP网络编程
ssize_t recv(int socket, const void *buffer, size_t length, int flags);

返回值:
成功:实际接收的字节数
失败:-1, 并设置errno
头文件:
#include <sys/socket.h>
buffer : 发送缓冲区首地址
length : 发送的字节数
flags : 接收方式(通常为0)

read()/write()

ssize_t read(int fd, void *buf, size_t count);

ssize_t write(int fd, const void *buf, size_t count);
  • read()和write()经常会代替recv()和send(),通常情况下,看程序员的偏好
  • 使用read()/write()和recv()/send()时最好统一

sendto(),recvfrom()

ssize_t sendto(int socket, void *message, size_t length, int flags, struct sockaddr *dest_addr, socklen_t dest_len);

ssize_t recvfrom(int socket, void *buffer, size_t length, int flags, struct sockaddr *address, socklen_t *address_len);
  • 这两个函数一般在使用UDP协议时使用

套接字的关闭

int close(int sockfd);

关闭双向通讯

int shutdown(int sockfd, int howto);
  • TCP连接是双向的(是可读写的),当我们使用close时,会把读写通道都关闭,有时侯我们希望只关闭一个方向,这个时候我们可以使用shutdown。
  • 针对不同的howto,系统回采取不同的关闭方式。
  • shutdown()的howto参数
    howto = 0
    关闭读通道,但是可以继续往套接字写数据。
    howto = 1
    和上面相反,关闭写通道。只能从套接字读取数据。
    howto = 2
    关闭读写通道,和close()一样

TCP编程API

Linux网络编程基础-03_TCP/IP网络编程
Linux网络编程基础-03_TCP/IP网络编程

示例:
server.c

#include "Net.h"
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(int argc, const char *argv[])
{
	int socket_fd = -1;
	int fd_w = -1;	

	if ((fd_w = open("read.txt", O_WRONLY|O_CREAT|O_TRUNC|O_APPEND, 0666)) < 0)
	{
		perror("open");
		exit(1);
	}
	//Step1:创建socket 文件描述符 
	if ((socket_fd = socket (AF_INET, SOCK_STREAM, 0)) < 0) 
	{
		perror ("socket");
		exit (1);
	}
	
	//连接服务器	
	struct sockaddr_in sin;
	bzero (&sin, sizeof (sin));
	sin.sin_family = AF_INET;
	sin.sin_port = htons (SERV_PORT);	//网络字节序的端口号
	
	//把ip地址转化为用于网络传输的二进制数值
	if( inet_pton(AF_INET, SERV_IP_ADDR, (void *)&sin.sin_addr) != 1)
	{
		perror("inet_pton");
		exit(1);
	}
	
	//Step2-绑定
	if( bind(socket_fd, (struct sockaddr*)&sin, sizeof(sin)) < 0)
	{
		perror("bind");
		exit(1);
	}
	
	//Step3-调用listen() 
	if( listen(socket_fd, BACKLOG) < 0)
	{
		perror("listen");
		exit(1);
	}
	
	printf(" Server Starting... OK!\n ");
	
	int new_sockfd = -1;

	//通过程序获取刚建立连接的socket的客户端的IP地址和端口号
	struct sockaddr_in cin;
	socklen_t addrlen = sizeof(cin);
	
	//Step4:接受TCP连接
	if((new_sockfd = accept(socket_fd, (struct sockaddr*)&cin, &addrlen)) < 0)
	{
		perror("accept");
		exit(1);
	}
	
	char ipv4_addr[16];
	
	//将网络传输的二进制数值转化为成点分十进制的ip地址
	if( !inet_ntop(AF_INET, (void*)&cin.sin_addr, ipv4_addr, sizeof(cin)))
	{
		perror("inet_ntop");
		exit(1);
	}
	
	//用于将主机 unsigned short 型数据转换成网络字节顺序
	printf(" Client(%s:%d ) is connected!\n",ipv4_addr, ntohs(cin.sin_port));
	printf("Wait Client Send Data\n");

	//step5:进行数据读写
	int ret_sock = -1;
	int ret_write = -1;
	int ret_read = -1;

	char buf[BUFSIZ];
	char send_buf[BUFSIZ];
	char display_buf[BUFSIZ];
	while(1)
	{

#if 1
		bzero(buf, BUFSIZ);
		bzero(send_buf, BUFSIZ);
		bzero (display_buf, BUFSIZ);

		do{
			ret_read = read(new_sockfd, buf, BUFSIZ -1);
		}while(ret_read < 0 && EINTR == errno );
		
		if(ret_read < 0)
		{
			perror("read");
			exit (1);
		}

		if(!ret_read)
		{
			break;
		}
		printf("Receive data: %s\n", buf);
		if( !strncasecmp(buf, QUIT_STR, strlen(QUIT_STR)))
		{
			printf("Client is exiting!\n ");
			break;
		}
	
		printf("Server Input data: ");
		if(fgets (send_buf, BUFSIZ - 1, stdin) == NULL)
		{
			continue;
		}
		if( !strncasecmp(send_buf, QUIT_STR, strlen(QUIT_STR)))
		{
			printf("Server is exiting!\n ");
			break;
		}
		
		do {
			ret_sock = write (new_sockfd, send_buf, strlen (send_buf));
			
			//sprintf(display_buf,"Client: %s",buf);
			sprintf(display_buf,"Server: %s",send_buf);
			ret_write = write(fd_w, display_buf, strlen(display_buf));

		} while (ret_sock < 0 && EINTR == errno  && ret_write  < 0); 


		
		
#endif
	}
	
	//Step6:关闭套接字
	close(new_sockfd);
	close(socket_fd);

	
	exit(0);

}

Client.c

#include "Net.h"
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>


int main(int argc, const char *argv[])
{
	int socket_fd = -1;
	int fd_w = -1;	

	if ((fd_w = open("read.txt", O_WRONLY|O_CREAT|O_TRUNC|O_APPEND, 0666)) < 0)
	{
		perror("open");
		exit(1);
	}

	//Step1:创建socket 文件描述符 
	if((socket_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
	{
		perror("socket");
		exit(1);
	}

	//连接服务器	
	struct sockaddr_in sin;
	bzero(&sin, sizeof(sin));

	sin.sin_family = AF_INET;
	sin.sin_port = htons(SERV_PORT);//将主机字节序转化成网络字节序

	//把ip地址转化为用于网络传输的二进制数值
	if( inet_pton(AF_INET, SERV_IP_ADDR, (void*)&sin.sin_addr) != 1)
	{
		perror("inet_pton");
		exit(1);
	}

	//Step2:建立连接
	if (connect (socket_fd, (struct sockaddr *) &sin, sizeof (sin)) < 0) 		{
		perror ("connect");
		exit (1);
	}

	printf ("Client staring...OK!\n");
	

	//Step3:进行数据读写
	int ret_sock = -1; 
	int ret_write = -1;
	int ret_read = -1;
	char buf[BUFSIZ];
	char rece_buf[BUFSIZ];
	char display_buf[BUFSIZ];

	while(1)
	{
		
#if 1
		bzero (buf, BUFSIZ);
		bzero (rece_buf, BUFSIZ);
		bzero (display_buf, BUFSIZ);
		
		printf("Client Input data: ");
		if (fgets (buf, BUFSIZ - 1, stdin) == NULL) 
		{
			continue;
		}
		do {
			ret_sock = write (socket_fd, buf, strlen (buf));
			
			sprintf(display_buf,"Client: %s",buf);
			ret_write = write(fd_w, display_buf, strlen(display_buf));

		} while (ret_sock < 0 && EINTR == errno && ret_write < 0); 

		if (!strncasecmp (buf, QUIT_STR, strlen (QUIT_STR))) {	//用户输入了quit字符
			printf ("Client is exiting!\n");
			break;
		}
#endif

		//------------------------------------------------------------------
		do{
			ret_read =read(socket_fd, rece_buf, BUFSIZ -1);
		}while(ret_read < 0 && EINTR == errno );
		
		if(ret_read < 0)
		{
			perror("read");
			exit (1);
		}

		if(!ret_read)
		{
			break;
		}

		printf("Client Receive data: %s\n", rece_buf);
		if( !strncasecmp(rece_buf, QUIT_STR, strlen(QUIT_STR)))
		{
			printf("Server is exiting!\n ");
			break;
		}
		
	}
	
	//Step3:关闭套接字
	close(fd_w);
	close(socket_fd);

	exit(0);

}