socket网络编程
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 }