赞
踩
目录
1、为了区分同一主机上的多个进程,使用端口号来进行处理
2、端口号是一个2字节的无符号整数存储,取值范围【0,65535】
3、网络通信中两个“地址”,主机的地址——IP,进程的地址——端口号;
4、特殊的端口号:0-1023
由系统默认应用程序占用,编程不可使用
TCP 21端口:FTP文件传输服务
TCP 23端口:TELNET终端仿真服务
TCP 25端口:SMTP简单邮件传输服务
TCP 110端口:POP3邮局协议版本3
TCP 80端口:HTTP超文本传输服务
TCP 443端口:HTTPS加密超文本传输服务
UDP 53端口:DNS域名解析服务
UDP 69端口:TFTP文件传输服务特殊的端口函数,存储在linux中的 /etc/services文件中
5、编程可使用的:1024-49151
编程可使用的端口号
6、临时端口号:49152-65535
客服端运行时动态选择的,编程时若未指定端口号,会分配临时端口号
相关帮助指令 man 2 socket man 7 socket
- #include <sys/types.h> /* See NOTES */
- #include <sys/socket.h>
-
- int socket(int domain, int type, int protocol);
- 功能:为通信创建一个端点,并返回该端点的文件描述符
- 参数1:通信域
- Name Purpose Man page
- AF_UNIX, AF_LOCAL 本地通信,同一主机之间进程通信 详情请看man 7 unix
- AF_INET IPv4 提供的网络通信 详情请看man 7 ip
- AF_INET6 IPv6 提供的网络通信 详情请看man 7 ipv6
- 参数2:指定通信语义,可以由多个宏值使用位或连接
- SOCK_STREAM:表示提供TCP协议的传输方式
- SOCK_DGRAM:表示提供UDP协议的传输方式
- SOCK_NONBLOCK:套接字设置非阻塞属性
- 参数3:如果参数2中仅仅指定一个协议,那么参数3可以填0,如果指定多个,则参数3需要指定特定的协议
- TCP协议名称:IPPROTO_TCP
- UDP协议名称:IPPROTO_UDP
- 返回值:成功返回创建的套接字文件描述符,失败返回 -1并置位错误码
-
服务器端:
1)创建套接字1
2)给套接字1绑定服务器端端口号、ip地址
3)将套接字1的功能改为监听(套接字内部被改造,原本的收发缓冲区改为已连接、未连接队列),用于检测是否客服端连接(三次握手就发生在这一步)
4)阻塞等待连接,连接成功,创建套接字2,用于消息的收发
5)消息的发送与接收
6)关闭通信,可以由服务器端、客服端其中之一执行
客服端:
1)创建由于通信的套接字
2)绑定客服端端口号、ip地址
3)连接服务器端,连接成功进入未连接队列,马上从未连接队列向已连接队列转换,该过程非常迅速,但同时申请连接的数量过多(超过未连接队列大小)仍会阻塞
4)消息的发送与接收
5)关闭通信,可以由服务器端、客服端其中之一执行
- #include <sys/types.h> /* See NOTES */
- #include <sys/socket.h>
-
- int bind(int sockfd, const struct sockaddr *addr,
- socklen_t addrlen);
- 功能:位套接字分配名称
- 参数1:通过socket函数创建出来的套接字文件描述符
- 参数2:通用地址信息结构体,需要根据具体使用的地址族而定, struct sockaddr仅仅只是为了类型的强制转换,防止出现警告
- 跨主机间通信:man 7 ip
- struct sockaddr_in {
- sa_family_t sin_family; /* 表示通信域 */
- in_port_t sin_port; /* 端口号的网络字节序 */
- struct in_addr sin_addr; /* ip地址 */
- };
-
- /* Internet address. */
- struct in_addr {
- uint32_t s_addr; /* IP地址的网络字节序 */
- };
- 同一主机间通信:man 7 uninx
- struct sockaddr_un {
- sa_family_t sun_family; /* 表示通信域:AF_UNIX */
- char sun_path[108]; /* 套接字文件的地址 */
- };
-
- 参数3:参数2的大小
- 返回值:成功返回0,失败返回-1并置位错误码
- 注意关于bind的两个错误:
- 1、 Cannot assign requested address:表示IP地址填写错误,检查IP是否有问题
- 2、Address already in use:表示地址信息正在占用,可以调用函数快速重用,也可以等一会
-
- #include <sys/types.h> /* See NOTES */
- #include <sys/socket.h>
-
- int listen(int sockfd, int backlog);
- 功能:将套接字设置成被动监听状态,已接受客户端的连接请求
- 参数1:套接字文件描述符
- 参数2:容纳连接的队列的最大长度,一般填128
- 返回值:成功返回0,失败返回-1并置为错误码
-
- #include <sys/types.h> /* See NOTES */
- #include <sys/socket.h>
-
- int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
- 功能:用于阻塞接收客户端连接请求
- 参数1:服务器套接字文件描述符
- 参数2:用于接收对端地址信息结构体的指针
- 参数3:接收对端地址信息的长度
- 返回值:成功返回一个新的用于通信的套接字文件描述符,失败返回-1并置位错误码
- #include <sys/types.h>
- #include <sys/socket.h>
-
- ssize_t recv(int sockfd, void *buf, size_t len, int flags);
- 功能:从套接字中读取数据到buf中
- 参数1:用于通信的套接字文件描述符
- 参数2:接收数据后的容器地址
- 参数3:接收的数据的大小
- 参数4:是否阻塞接收
- 0:表示阻塞接收消息
- MSG_DONTWAIT:表示非阻塞接收数据
- 返回值:
- >0:表示成功读取的字符个数
- =0:表示通信对端已经下线
- =-1:表示出错,置位错误码
- #include <sys/types.h>
- #include <sys/socket.h>
-
- ssize_t send(int sockfd, const void *buf, size_t len, int flags);
- 功能:向通信套接字文件描述符中写入数据
- 参数1:通信的套接字文件描述符
- 参数2:要发送数据的起始地址
- 参数3:要发送数据的大小
- 参数4:是否阻塞接收
- 0:表示阻塞接收消息
- MSG_DONTWAIT:表示非阻塞接收数据
- 返回值:成功返回发送字符的个数,失败返回-1并置位错误码
- #include <sys/types.h> /* See NOTES */
- #include <sys/socket.h>
-
- int connect(int sockfd, const struct sockaddr *addr,
- socklen_t addrlen);
- 功能:将套接字文件描述符连接到addr指向的地址空间中
- 参数1:客户端套接字文件描述符
- 参数2:对端地址信息结构体
- 参数3:参数2的大小
- 返回值:成功返回0,失败返回-1并置位错误码
- #include <myhead.h>
- #define SER_PORT 6666
- #define SER_IP "192.168.232.129"
- int main(int argc, char const *argv[])
- {
- // 1、创建套接字
- int sfd = socket(AF_INET, SOCK_STREAM, 0);
- // 参数1:ipv4的网络通信
- // 参数2:TCP通信方式
- // 参数3:默认使用一个协议
-
- if (sfd == -1)
- {
- perror("socket error");
- return -1;
- }
-
- printf("socket success, sfd = %d\n", sfd); // 3
-
- // 2、为套接字绑定ip地址和端口号
- // 2.1 填充地址信息结构体
- struct sockaddr_in sin;
- sin.sin_family = AF_INET; // 通信域
- sin.sin_port = htons(SER_PORT); // 端口号
- sin.sin_addr.s_addr = inet_addr(SER_IP); // ip地址
-
- // 2.2 绑定
- if (bind(sfd, (struct sockaddr *)&sin, sizeof(sin)) == -1)
- {
- perror("bind error");
- return -1;
- }
- printf("bind success\n");
-
- // 3、将套接字设置为被动监听状态,用于接收
- if (listen(sfd, 128) == -1)
- {
- perror("listen error");
- return -1;
- }
- printf("listen success\n");
-
- // 4、阻塞等待客户端的连接请求
- // 4.1 定义n变量用于e接收客服端的信息
- struct sockaddr_in cin;
- socklen_t addrlen = sizeof(cin);
-
- // 4.2 接收连接
- int newsfd = accept(sfd, (struct sockaddr *)&cin, &addrlen);
- if (newsfd == -1)
- {
- perror("accept error");
- return -1;
- }
- printf("[%s:%d]:accept on\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port));
-
- //5、数据收发
- char buf[128] = "";
-
- while (1)
- {
- // 从客户端套接字中接收数据
- int res = recv(newsfd, buf, sizeof(buf),0);
- if (res == -1)
- {
- perror("read error");
- return -1;
- }
- else if (res == 0)
- {
- printf("客户端已下线\n");
- close(newsfd); // 关闭客户端套接字
- break;
- }
-
- // 接收数据
- printf("[%s:%d]:%s\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), buf);
-
- // 对接收到的数据进行处理
- strcat(buf, ":D");
-
- // 将消息返回到客户端
- if (send(newsfd, buf, strlen(buf),0) == -1)
- {
- perror("发送失败\n");
- return -1;
- }
- printf("发送成功\n");
- bzero(buf,sizeof(buf));//清空容器
- }
- return 0;
- }
- #include <myhead.h>
- #define SER_PORT 6666 // 与服务器保持一致
- #define SER_IP "192.168.232.129" // 服务器ip地址
- #define CLI_PORT 8888 // 客服端端口号
- #define CLI_IP "192.168.232.129" // 客服端ip地址
- int main(int argc, char const *argv[])
- {
- //1、 创建用于通信的套接字文件描述符
- int cfd = socket(AF_INET, SOCK_STREAM, 0);
- if (cfd == -1)
- {
- perror("socket error");
- return -1;
- }
- printf("cfd = %d\n", cfd);
- //2、 绑定IP地址和端口号
- struct sockaddr_in cin;
- cin.sin_family = AF_INET; // 通信域
- cin.sin_port = htons(CLI_PORT); // 端口号
- cin.sin_addr.s_addr = inet_addr(CLI_IP); // ip地址
-
- //2.2、 绑定
- if (bind(cfd, (struct sockaddr *)&cin, sizeof(cin)) == -1)
- {
- perror("bind error");
- return -1;
- }
- printf("bind success\n");
- //3、 连接服务器
- //3.1、 填充服务器地址信息结构体
- struct sockaddr_in sin;
- sin.sin_family = AF_INET; //通信域
- sin.sin_port = htons(SER_PORT);
- sin.sin_addr.s_addr = inet_addr(SER_IP);
-
- //3.2、连接服务器
- if(connect(cfd,(struct sockaddr*)&sin,sizeof(sin)) == -1)
- {
- perror("connect error");
- return -1;
- }
- printf("连接服务器成功\n");
-
- //4、数据收发
- char buf[128] = "";
- while (1)
- {
- printf("输入:");
- fgets(buf,sizeof(buf),stdin);
- buf[strlen(buf)-1] = 0;
-
- //将数据发送到服务器
- send(cfd,buf,strlen(buf),0);
- printf("发送结束\n");
-
- //接收服务器发送的数据
- bzero(buf,sizeof(buf));//清空容器
- recv(cfd,buf,sizeof(buf),0);
- printf("收到服务器信息:%s\n",buf);
- }
-
- //5、关闭套接字
- close(cfd);
- return 0;
- }
- ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
- struct sockaddr *src_addr, socklen_t *addrlen);
- //功能:从套接字文件描述符中读取数据,并将对端地址信息结构体接收
- 参数1:套接字文件描述符
- 参数2:要接收数据的起始地址
- 参数3:要接收的数据大小
- 参数4:是否阻塞,0表示阻塞,MSG_NOWAIT表示非阻塞
- 参数5:接收对端地址信息结构体
- 参数6:参数5的大小
- 返回值:成功返回读取的字节的大小,失败返回-1并置位错误码
-
- ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
- const struct sockaddr *dest_addr, socklen_t addrlen);
- //功能:向套接字文件描述符中读取数据,写给指定的对端接收
- 参数1:套接字文件描述符
- 参数2:要发送数据的起始地址
- 参数3:要发送的数据大小
- 参数4:是否阻塞,0表示阻塞,MSG_NOWAIT表示非阻塞
- 参数5:接收对端地址信息结构体
- 参数6:参数5的大小
- 返回值:成功返回发送的字节的大小,失败返回-1并置位错误码
-
- #include <myhead.h>
- #define SER_PORT 9999 // 服务器端口号
- #define SER_IP "192.168.232.129" // 服务器ip地址
- int main(int argc, char const *argv[])
- {
- // 1、创建用于通信的套接字i文件描述符
- int sfd = socket(AF_INET, SOCK_DGRAM, 0);
- if (sfd == -1)
- {
- perror("scoket error");
- return -1;
- }
- printf("sfd = %d\n", sfd); // 3
- // 2、绑定ip地址和端口号
- // 2.1 填充地址信息结构体
- struct sockaddr_in sin;
- sin.sin_family = AF_INET; // 通信域
- sin.sin_port = htons(SER_PORT); // 端口号
- sin.sin_addr.s_addr = inet_addr(SER_IP); // ip地址
-
- // 2.2、 绑定
- if (bind(sfd, (struct sockaddr *)&sin, sizeof(sin)) == -1)
- {
- perror("bind error");
- return -1;
- }
- printf("bind success\n");
- // 3、数据收发
- char buf[128] = "";
-
- struct sockaddr_in cin; // 接收对端地址信息
- socklen_t addrlen = sizeof(cin); // 接收地址长度
-
- while (1)
- {
- // 清空容器
- bzero(buf, sizeof(buf));
-
- // 从套接字中读取数据
- recvfrom(sfd, buf, sizeof(buf), 0,(struct sockaddr*)&cin,&addrlen);
- printf("收到信息:%s\n", buf);
-
- // 处理收到的信息
- strcat(buf, ":(");
-
- if (sendto(sfd, buf, sizeof(buf), 0,(struct sockaddr*)&cin,sizeof(cin)) == -1)
- {
- perror("send error");
- return -1;
- }
- printf("发送成功\n");
- }
-
- // 4、关闭文件描述符
- close(sfd);
- return 0;
- }
- #include <myhead.h>
- #define SER_PORT 9999 // 与服务器保持一致
- #define SER_IP "192.168.232.129" // 服务器ip地址
- #define CLI_PORT 5555 // 客服端端口号
- #define CLI_IP "192.168.232.129" // 客服端ip地址
- int main(int argc, char const *argv[])
- {
- // 1、 创建用于通信的套接字文件描述符
- int cfd = socket(AF_INET, SOCK_DGRAM, 0);
- if (cfd == -1)
- {
- perror("socket error");
- return -1;
- }
- printf("cfd = %d\n", cfd);
- // 2、 绑定IP地址和端口号
- struct sockaddr_in cin;
- cin.sin_family = AF_INET; // 通信域
- cin.sin_port = htons(CLI_PORT); // 端口号
- cin.sin_addr.s_addr = inet_addr(CLI_IP); // ip地址
-
- // 2.2、 绑定
- if (bind(cfd, (struct sockaddr *)&cin, sizeof(cin)) == -1)
- {
- perror("bind error");
- return -1;
- }
- printf("bind success\n");
-
- // 3、数据收发
- char buf[128] = "";
-
- // 3.1 填充服务器地址信息结构体
- struct sockaddr_in sin; // 接收对端地址信息
- sin.sin_family = AF_INET; // 服务器的通信域
- sin.sin_port = htons(SER_PORT); // 服务器的端口号
- sin.sin_addr.s_addr = inet_addr(SER_IP); // 服务器的ip地址
-
- while (1)
- {
- printf("输入:");
- fgets(buf, sizeof(buf), stdin);
- buf[strlen(buf) - 1] = 0;
-
- sendto(cfd, buf, strlen(buf), 0, (struct sockaddr *)&sin, sizeof(sin));
-
- printf("发送成功\n");
-
- bzero(buf, sizeof(buf));
- recvfrom(cfd, buf, sizeof(buf), 0, NULL, NULL);
- printf("收到服务器信息:%s\n", buf);
- }
-
- // 4、关闭套接字
- close(cfd);
- return 0;
- }
1、无论时TCP还是UDP通信中,服务器必须绑定ip地址和端口号,以便于让客服端找到该服务器。对于客服端而言,ip地址和端口号可以不绑定,若不绑定,端口号由系统动态分配(49152-65535)
2、对于TCP通信而言,可以使用recv和send进行通信,也可以使用read、write进行通信,还可以使用sendto和recvfrom进行通信
3、对于UDP通信而言,如果当前端只是用于接收数据,不发送数据,可以使用recvfrom、recv、read进行接收;如果当前端接收数据后还要发送数据给对端,则需要使用recvfrom进行接收数据,以便接收对端地址信息结构体
4、UDP通信中,服务器端可以使用connect函数与指定的客服端建立一个唯一的通道,在解除这种连接前,其他客服端与服务器端间不能通信。可通过将与服务器端建立连接的那个客服端的地址消息结构体中的sin.family设置未 AF_UNSPEC, 后再次使用connect函数断开连接
UDP中通信使用connect连接的好处:
1)提高信息传输效率、完整度
例如:A和B同时向服务器发送消息,但是A发送的消息较大,需要较长的时间,发送过程中可能会出现时间片用完,服务器转而接收B的消息的情况,这会导致消息混乱。这时就可以先单独跟A建立连接,等所有数据传输结束后,再跟B通信
2)传输性能高
一般的UDP通信:获取对端地址信息 -->将信息加载到内核 -->数据收发--->获取对端地址信息 -->将信息加载到内核 --->数据收发 --->获取对端地址信息 -->将信息加载到内核 -->数据收发 -->......
会经历多次用户空间到内核空间的转换,该过程对于cpu而言是一个漫长的过程
UDP建立连接后:获取对端地址信息 ->将信息加载到内核 ->数据收发 ->数据收发 >数据收发 >数据收发完成>进行其他对端的信息处理.....
会有效的减少用户空间到内核空间的转换次数
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。