网络编程-TCP服务器,TCP客户端

socket网络编程

socket网络编程预备知识
IP地址
端口号
字节序
Socket

socket

socket简介

(1)套接字(socket)是一个抽象层的独立于协议的网络编程接口,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。套接字允许应用程序将I/O插入到网络中,并与网络中的其他应用程序进行通信。

(2)网络套接字是IP地址与端口号的组合,套接字Socket =(IP地址:端口号)。每一个传输层连接唯一的被通信两端的两个端点(即两个套接字)所确定。
即:套接字可以看成是两个网络应用程序进行通信时,各自通信连接中的一个端点。通信时,其中的一个网络应用程序将要传输的一段信息写入它所在主机的Socket中,该Socket通过网络接口层的传输介质将这段信息发送给另一台主机的Socket中,使这段信息能传送到其他程序中。因此,两个应用程序之间的数据传输要通过套接字来完成。

(3)BSD Socket(伯克利套接字)是通过标准的UNIX文件描述符和其它程序通讯的一个方法,目前已经被广泛移植到各个平台,socket是各种平台下,一套通用的接口。

socket特点

(1)独立于协议的网络编程接口:和协议栈无关,是一组API接口
(2)既可以实现网络通信也可以实现IPC通信。不是所有的网络通信都必须走Socket!
(3)切记 Socket编程 != TCP/IP编程

socket类型

为了满足不同的通信程序对通信质量和性能的要求,一般的网络系统提供了三种不同类型的套接字,
以供用户在设计网络应用程序时根据不同的要求来选择。这三种套接字类型为:

(一)流式套接字(SOCK_STREAM)
流式套接字 是一种面向连接、提供可靠的通信、基于字节流的双向数据传输服务。实现数据无差错、无重复的发送、保证接收端按发送顺序接收。流式套接字内设流量控制,避免淹没接收较慢的接收方。数据看成字节流,无长度限制。针对TCP协议

(二)数据报套接字(SOCK_DGRAM)
数据报套接字 是一种无连接、不可靠的双向数据传输服务。数据包以独立的形式发送,数据可能会出现丢失、失序、重复到达,不保证无差错。针对UDP协议

(三)原始套接字(SOCK-RAW)
该套接字允许对较低层协议(如IP或ICMP)进行直接访问,常用于网络协议分析,检验新的网络协议实现,也可用于测试新配置或安装的网络设备

socket网络编程在网络协议中的位置

应用层和传输层之间
在这里插入图片描述
在这里插入图片描述

TCP服务器端和客户端

TCP通信流程

在这里插入图片描述

TCP服务器端

服务器是被动的接收连接并提供服务。
在这里插入图片描述

一、socket函数

NAME
       socket - create an endpoint for communication	
       linux系统下一切皆文件,socket创建一个通信端点的特殊文件

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

       int socket(int domain, int type, int protocol);
       一般用法:
       sockfd = socket(AF_INET, SOCK_STREAM, 0);

一、参数
(1)domain:地址族,一般写: AF_INET 、AF_UNIX

  	Name            	    Purpose                     Man page
   AF_UNIX, AF_LOCAL   Local communication              unix(7)		用于进程间通信
   AF_INET             IPv4 Internet protocols          ip(7)		IPV4地址
   AF_INET6            IPv6 Internet protocols          ipv6(7)		IPV6地址
   AF_NETLINK          Kernel user interface device     netlink(7)	内核和用户通信
   AF_PACKET           Low level packet interface       packet(7)	用户自己定义数据包

(2)type:socket套接字类型
TCP(SOCK_STREAM)、UDP(SOCK_DGRAM)、ICMP(SOCK_RAW)

(3)protocol:指定的协议。如果调用者不想指定,默认写0。
常用的协议有,IPPROTO_TCP、IPPROTO_UDP、IPPROTO_STCP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。
二、返回值
成功:在linux文件系统中创建生成了一个socket文件描述符sockfd,用于套接字绑定、监听和接收客户端连接的sockfd。
失败:-1
在这里插入图片描述

二、bind函数

绑定IP地址和端口号于sockfd文件,表明在网络通信中此socket的具体位置。


   #include <sys/types.h>         
   #include <sys/socket.h>

   int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
   一般用法:
   bind(sockfd, (struct sockaddr*)(&myaddr), sizeof(myaddr));
   

(一)参数
(1)addr(socketaddr):指向struct sockaddr结构的指针,包含本机IP 地址(虚拟机的IP地址)和端口号(自定义port)。为了方便,以struct sockaddr_in 代替struct sockaddr结构体,填充地址信息。
(2)addrlen:sockaddr地址结构的长度,addrlen = sizeof (struct sockaddr_in);
注意:
(1)IP地址4字节,端口号2字节都是多字节数据,因此都需要转换成网络字节序(大端)。
(2)虚拟机桥接模式下的网关要配置和windows下主机相同的网关。

	端口号:htons(端口号);
	IP地址:inet_addr(”IP地址“)
	通用地址结构体:
   struct sockaddr//16个字节 
   {
          sa_family_t  sa_family;
               char  sa_data[14];
   };
   
   	Internet协议地址结构体:头文件#include <netinet/in.h>
	struct sockaddr_in //16个字节
  {           
       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,作为填充
  }; 
  
	struct in_addr//4字节16位,填写IPV4地址
	{
	     in_addr_t  s_addr;    // u32 network address 
	};


1、字节序转换函数
一、IP地址转换
(1)功能:点分十进制IP转换网络字节序IP:inet_addr函数
 #include <arpa/inet.h>
原型: 
in_addr_t inet_addr(const char *cp);

eg: myaddr.sin_addr.s_addr = inet_addr(192.168.1.100)2)功能:将一个十进制网络字节序转换为点分十进制IP格式的字符串。
 #include <arpa/inet.h>
原型:
		char*inet_ntoa(struct in_addr in);


二、端口号:
(1)主机字节序到网络字节序
	u_long htonl (u_long hostlong);
	u_short htons (u_short hostshort);2)网络字节序到主机字节序
	u_long ntohl (u_long netlong);
	u_short ntohs (u_short netshort);
	
2、地址结构的一般用法
1、定义一个struct sockaddr_in类型的变量并清空
struct sockaddr_in myaddr;
memset(&myaddr, 0, sizeof(myaddr));

2、IPV4填充地址信息
myaddr.sin_family = AF_INET;//地址族
myaddr.sin_port   = htons(8888); //指定端口号
myaddr.sin_addr.s_addr = inet_addr(192.168.1.100);//虚拟机IP地址,ifconfig

3、将该变量强制转换为struct sockaddr类型在函数中使用
bind(sockfd, (struct sockaddr*)(&myaddr), sizeof(myaddr));

在这里插入图片描述
二、返回值
成功:0
失败:-1
在这里插入图片描述

三、listen函数

功能:完成listen()调用后,socket主动属性变成了被动的监听listening socket,此函数不阻塞


   #include <sys/types.h>          /* See NOTES */
   #include <sys/socket.h>
   
   int listen(int sockfd, int backlog);
   
   RETURN VALUE
       On success, zero 0 is returned.  
       On error, -1 is returned, and errno is set appropriately.

参数
(1)sockfd:指定监听连接的套接字
(2)backlog:指定了完全连接成功的最大队列长度,它的作用:在于处理可能同时出现的几个连接请求。此参数现在基本已经不用。一般写5或10

四、accept函数

功能:阻塞等待客户端的请求连接。

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

       int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
       

RETURN VALUE
       On success, these system calls return a nonnegative integer 
       that is a descriptor for the accepted socket.  
       On error, -1 is returned, and errno is set appro‐priately.
       如果成功,这些系统调用将返回一个非负整数这是接受的套接字的描述符。
	   出错时,返回-1,并单独设置errno。

参数:
(1)sockfd:指定监听连接的sockfd
(2)addr:(struct sockaddr *)& client_addr 客户端地址,即监听的对方地址填充于结构体struct sockaddr中。
(3)addrlen:对方(客户端)地址长度,addrlen = sizeof (struct sockaddr);

返回值:
成功:**返回已建立好连接的套接字clientfd,这个clientfd是用于和客户端通信连接的端点。用于和客户端的读和写。**操作方式和I/O文件操作一样。
失败:-1,设置errno

在这里插入图片描述

五、recv函数

recv函数功能:阻塞等待接收从客户端发送来的数据。
注:recv函数是对read函数的封装,在网络通信中,有阻塞属性。

SYNOPSIS
       #include <sys/types.h>
       #include <sys/socket.h>

       ssize_t recv(int sockfd, void *buf, size_t len, int flags);
       
RETURN VALUE
       These calls return the number of bytes received, or -1 if an error occurred.
	   The return value will be 0 when the peer has performed an orderly shutdown.
	   这些调用返回接收到的字节数,如果发生错误则返回-1。
	   当断开和客户端的连接,即对方的通信端关闭时,则返回值为0

参数
sockfd:accept返回已建立好连接的套接字clientfd,这个clientfd是用于和客户端通信连接的端点。用于和客户端的读和写。
buf:接收缓冲区首地址
len:接收最长的字节数
flags:接收方式,通常为0:指定系统默认的接收方式

recv函数的返回值
成功连接:调用返回实际接收到的字节数size_num。(recv()阻塞等待接收数据)
断开连接:当断开和客户端的连接,即对方的通信端关闭时,则返回值为0。(断开连接时,recv函数不再阻塞,此时应该关闭sockfd)
失败:错误返回 -1。

注:read()和write()经常会代替recv()和send()的使用。通常情况下,根据程序员的偏好使用read()/write()和recv()/send()时,最好统一匹配。

在这里插入图片描述

六、关闭套接字

一、close()

关闭双向通讯

	int close(int sockfd);

二、shutdown()

TCP连接是双向的(是可读写的),当我们使用close时,会把读写通道都关闭,有时侯我们希望只关闭一个方向,这个时候我们可以使用shutdown。


	int shutdown(int sockfd, int howto);
	

针对不同的howto参数,系统回采取不同的关闭方式。
(1)howto = 0:关闭读通道。仍可以继续往套接字写数据。
(2)howto = 1:关闭写通道。只能从套接字读取数据。
(3)howto = 2:关闭读写通道,和close()一样

TCP客户端

客户端是主动请求给予服务。
在这里插入图片描述

一、connect函数

NAME
       connect - initiate a connection on a socket
       发起连接请求

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

       int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
       
       RETURN VALUE
       If the connection or binding succeeds, zero 0 is returned.  
       On error, -1 is returned, and errno is set appropriately.
 

参数
addr:指定连接服务器端的serveraddr

二、send函数

SYNOPSIS
       #include <sys/types.h>
       #include <sys/socket.h>

       ssize_t send(int sockfd, const void *buf, size_t len, int flags);

RETURN VALUE
       On success, these calls return the number of characters sent.  
       On error, -1 is returned, and errno is set appropriately.
返回值
成功后,这些调用将返回发送的字符数。如果出现错误,则返回-1,并相应地设置errno。

TCP服务器端代码

利用线程,实现服务器并发接收客户端的连接

  1 #include <stdio.h>
  2 #include <sys/types.h>
  3 #include <sys/socket.h>
  4 #include <netinet/in.h>
  5 #include <arpa/inet.h>
  6 #include <string.h>
  7 #include <pthread.h>
  8 #include <errno.h>
  9 #include <unistd.h>                                                                                                                                                 
 10 #include <stdlib.h>
 11 
 12 
 13 #define NUM 32
 14 
 15 void * mypthread(void * sig);//线程处理函数
 16 /*
 17 struct sockaddr_in //16个字节
 18 {           
 19     u_short sin_family;       // 地址族, AF_INET,2 bytes
 20     u_short sin_port;        // 端口,2 bytes
 21     struct in_addr sin_addr;  // IPV4地址,4 bytes 
 22     char sin_zero[8];         // 8 bytes unused,作为填充
 23 };
 24 
 25 struct in_addr//4字节16位,填写IPV4地址
 26 {
 27     in_addr_t  s_addr;    // u32 network address 
 28 };
 29 
 30 */
 31 
 32 int main(int argc, const char *argv[])
 33 {
 34     int sockfd;
 35     int ret_bind, ret_listen;
 36     struct sockaddr_in server_addr;
 37     socklen_t len;
 38     int client_fd;
 39     struct sockaddr_in client_addr;
 40     socklen_t addrlen;
 41     int re;
 42     pthread_t tid;
 43 
 44     sockfd = socket(AF_INET, SOCK_STREAM, 0);//创建一个sockfd套接字文件描述符
 45     if(sockfd < 0)
 46     {
 47         perror("socket");
 48         return -1;
 49     }
 50     printf("sockfd = %d\n", sockfd);
 51                                                                                                                                                                     
 52     server_addr.sin_family = AF_INET;//对声明的serveraddr变量的结构体进行属性填充
 53     server_addr.sin_port   = htons(12345);//端口号主机字节序转网络字节序
 54     server_addr.sin_addr.s_addr = inet_addr("192.168.2.160");//点分十进制转网络字节序
 55     len = sizeof(server_addr);
 56 
 57     ret_bind = bind(sockfd, (struct sockaddr *)&server_addr, len);//绑定ip地址和端口号
 58     if(ret_bind == -1)
 59     {
 60         perror("bind");
 61         return -1;
 62     }
 63 
 64     ret_listen = listen(sockfd, 10);//将指定的socket主动行为变成被动的监听状态
 65     if(ret_listen == -1)
 66     {
 67         perror("listen");
 68         return -1;
 69     }
 70 
 71     addrlen = sizeof(client_addr);
 72 
 73     while(1)//并发处理时,实现同一时间,接收多个客户端的请求
 74     {
 			//阻塞等待接收客户端的连接
 75         client_fd = accept(sockfd, (struct sockaddr *)&client_addr, &addrlen);
 76         if(client_fd == -1)
 77         {
 78             perror("accept");                                                                                                                                       
 79             return -1;
 80         }
 			//打印对方客户端的ip地址和端口号port
 81         printf("client_fd = %d, client_addr = %s: client_port = %d\n", \
 82                 client_fd, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
 83 		//创建一个线程,用于并发处理客户端的连接,实现同一时间,接收多个客户端的请求
 84         re = pthread_create(&tid, NULL, mypthread, (void *)client_fd);
 85         if(re != 0)
 86         {
 87             printf("pthread_create errno : %s\n",strerror(errno));
 88             return -1;
 89         }
 90 
 91     }
 92 
 93 
 94     return 0;
 95 }
 96 
 97 void * mypthread(void * sig)//线程处理函数
 98 {
 99     int client_fd;
100     char buf[NUM];
101     int size_num;
102     client_fd = (int)sig;//将参数强转成int
103     pthread_detach(pthread_self());//设置线程自动回收
104 
105     while(1)//将连接上的客户端,接收数据的传输。
106     {
107         memset(buf, 0, NUM);
108         size_num = recv(client_fd, buf, 32, 0);
109         if(size_num > 0)
110         {
111             printf("size_num = %d, recv = %s\n",size_num, buf);//打印发送过来的内容
112         }
113         else if(size_num == 0)//断开连接,recv函数不再阻塞
114         {
115             printf("connect is shutdown\n");
116             sleep(1);
117             close(client_fd);
118             break;
119         }                                                                                                                                                           
120         else//错误退出
121         {
122             perror("recv");
123             exit(-1);
124         }
125     }
126 }
~                                       

TCP客户端代码

 1 #include <stdio.h>
  2 #include <sys/types.h>
  3 #include <sys/socket.h>
  4 #include <netinet/in.h>
  5 #include <arpa/inet.h>
  6 #include <string.h>
  7 #include <pthread.h>
  8 #include <errno.h>
  9 #include <unistd.h>
 10 #include <stdlib.h>
 11 
 12 
 13 #define NUM 32
 32 int main(int argc, const char *argv[])
 33 {
 34     int sockfd;
 35     int ret_bind;
 36     struct sockaddr_in client_addr;
 37     socklen_t len;
 38     struct sockaddr_in server_addr;
 39     socklen_t addrlen;                                                                                                                                              
 40     int ret;
 41     size_t s_num;
 42     char buf[NUM];
 43     size_t length;
 44 
 45     sockfd = socket(AF_INET, SOCK_STREAM, 0);
 46     if(sockfd < 0)
 47     {
 48         perror("socket");
 49         return -1;
 50     }
 51     printf("sockfd = %d\n", sockfd);
 52 
 53     client_addr.sin_family = AF_INET;
 54     client_addr.sin_port   = htons(60000);
 55     client_addr.sin_addr.s_addr = inet_addr("192.168.2.160");
 56     len = sizeof(client_addr);
 57 
 58     ret_bind = bind(sockfd, (struct sockaddr *)&client_addr, len);
 59     if(ret_bind == -1)
 60     {
 61         perror("bind");
 62         return -1;
 63     }
 64     else if(ret_bind == 0)
 65     {
 66         printf("i have bind\n");
 67     }
68 
 69 
 70     server_addr.sin_family = AF_INET;
 71     server_addr.sin_port   = htons(12345);
 72     server_addr.sin_addr.s_addr = inet_addr("192.168.2.160");                                                                                                       
 73     addrlen = sizeof(server_addr);
 74 
 75 
 76     ret = connect(sockfd, (struct sockaddr *)&server_addr, addrlen);
 77     if(ret == -1)
 78     {
 79         perror("connect");
 80         return -1;
 81     }
 82     else if(ret == 0)
 83     {
 84         printf("i have bind\n");
 85     }
 86 
 87     while(1)
 88     {
 89         memset(buf, 0, NUM);
 90         fgets(buf, NUM, stdin);
 91         length = strlen(buf);
 92         s_num = send(sockfd, buf, length, 0);
 93         if(s_num == -1)
 94         {
 95             perror("send");
 96             return -1;
 97         }
 98         else if(s_num > 0)
 99         {
100             printf("s_num = %d, buf: %s\n", s_num, buf);
101         }
102 
103     }
105 
106     return 0;
107 }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值