-
2019-12-17 09:49:22
Socket
服务器:Server
为其他人提供相应服务的电脑
- 硬件:配置要求极极极极极极极极极高的电脑,通常没有显示器;多台服务器的集合叫做服务器集群
- 软件:Linux系统;有一个相关的软件(Tomcat、SVN、数据库…);
网络编程模式
- C/S:Client-Server,客户端-服务器
- B/S:Broswer-Server,浏览器-服务器
Socket属于C/S模式
访问网站的过程
以百度为例
- 访问域名:www.baidu.com
- DNS解析:由DNS解析域名,返回对应的IP地址给浏览器。DNS相当于一座桥,架在访问者与被访问对象之间,提供高效的访问速率。
- 浏览器获得IP地址后,向服务器发送访问请求。
在命令行中使用:ping [域名],可以快速获得对应域名的IP地址(前提是能正常访问服务器)
Socket通信
Socket用到的类:java.net
- Socket类:客户端套接字
- ServerSocket:服务器套接字
套接字:类似于网线两端的接口
服务器
package day20191215; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.Date; import java.util.Scanner; public class Server { public static void main(String[] args) { ServerSocket server = null; Socket s = null; try { server = new ServerSocket(9999); //正常情况下,服务器接收一次来自客户端的信息就会自行关闭 //现在使用while(true)使服务器一直接收来自客户端的信息 System.out.println("服务器已开启,等待连接..."); s = server.accept(); System.out.println("已连接,当前客户端:"+s); //服务器输入当前系统时间给客户端 PrintWriter setTime = new PrintWriter(s.getOutputStream()); setTime.println(new Date()); setTime.flush(); while(true) { //服务器接收来自客户端的信息 BufferedReader getMsg = new BufferedReader(new InputStreamReader(s.getInputStream())); String msg = getMsg.readLine(); System.out.println(msg); //发送信息 Scanner scan = new Scanner(System.in); PrintWriter sendMsg = new PrintWriter(s.getOutputStream()); sendMsg.println(scan.nextLine()); sendMsg.flush(); } } catch (IOException e) { e.printStackTrace(); }finally { try { //关闭服务器流,Socket本质是IO流 server.close(); } catch (IOException e) { e.printStackTrace(); } } } }
客户端
package day20191215; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Socket; import java.net.UnknownHostException; import java.util.Scanner; public class Client { public static void main(String[] args) { try { /** * 连接服务器必须要传入两个参数:IP地址+端口 * 本机IP地址统一为127.0.0.1(或者使用localhost) * 端口不取太小,以免被占用 */ Socket s = new Socket("localhost", 9999); //获取从服务器传递过来的信息 BufferedReader getTime = new BufferedReader(new InputStreamReader(s.getInputStream())); String str = getTime.readLine(); System.out.println(str); while(true) { //向客户段发送信息 Scanner scan = new Scanner(System.in); PrintWriter sendMsg = new PrintWriter(s.getOutputStream()); sendMsg.println(scan.nextLine()); sendMsg.flush(); //接收信息 BufferedReader getMsg = new BufferedReader(new InputStreamReader(s.getInputStream())); String msg = getMsg.readLine(); System.out.println(msg); } } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
只支持一问一答(客户端先问,服务器再答),如果想做成类似聊天室的功能,可以用线程的相关知识。
更多相关内容 -
socket套接字介绍
2019-11-12 20:58:59由于最近要完成自己的项目作业,其中需要使用socket进行网络通信。所以简单了解了一些这方面的知识,希望可以抛砖引玉。 0x01 socket介绍 socket 的原意是“插座”,在计算机通信领域,socket 被翻译为“套接字”,...0x00 背景介绍
由于最近要完成自己的项目作业,其中需要使用socket进行网络通信。所以简单了解了一些这方面的知识,希望可以抛砖引玉。
0x01 socket介绍
socket 的原意是“插座”,在计算机通信领域,socket 被翻译为“套接字”,它是计算机之间进行通信的一种约定或一种方式。通过 socket 这种约定,一台计算机可以接收其他计算机的数据,也可以向其他计算机发送数据。我们把插头插到插座上就能从电网获得电力供应,同样,为了与远程计算机进行数据传输,需要连接到因特网,而 socket 就是用来连接到因特网的工具。
套接字(socket)是一个抽象层,网络套接字是IP地址与端口的组合。学习 socket,也就是学习计算机之间如何通信,并编写出实用健壮的程序。
套接字有很多种类,而最具代表性的就是 Internet 套接字,我们常说的套接字,如果没有其他说明,都是指 Internet 套接字。0x02 socket类型
根据数据的传输方式,可以将套接字分成两种类型。流格式套接字(SOCK_STREAM)和数据报格式套接字(SOCK_DGRAM)。通过 socket() 函数创建连接时,必须告诉它使用哪种数据传输方式。
流格式套接字是基于TCP协议,TCP 协议会控制你的数据按照顺序到达并且没有错误。所以说流格式套接字是一种可靠的、双向的通信数据流。
流格式套接字有以下特征:数据在传输过程中不会消失;
数据是按照顺序传输的;
数据的发送和接收不是同步的数据报格式套接字也是 IP 协议作路由,但是是依托UDP协议。数据报套接字是一种不可靠的、不按顺序传递的、以追求速度为目的的套接字。当数据传输发生错误时,也不会重传。
数据报格式套接字有以下特征:强调快速传输而非传输顺序;
传输的数据可能丢失也可能损毁;
限制每次传输的数据大小;
数据的发送和接收是同步的。0x03 网络通信协议
socket是站在传输层的基础上,所以可以使用 TCP/UDP 协议,为了更好的了解socket,我们需要网络通信协议知识的储备,我把附件已经放在下面了,大家可以自取。至少要了解TCP/IP协议,关于TCP的三次握手(这也是面试常问的问题),
0x04 linux与windows系统下socket程序演示
linux系统下比较简单,我们实现一个简单的客户端从服务器读取“Hello World”并打印出来
服务器:#include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <netinet/in.h> int main(){ //创建套接字 int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //将套接字和IP、端口绑定 struct sockaddr_in serv_addr; memset(&serv_addr, 0, sizeof(serv_addr)); //每个字节都用0填充 serv_addr.sin_family = AF_INET; //使用IPv4地址 serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具体的IP地址 serv_addr.sin_port = htons(1234); //端口 bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); //进入监听状态,等待用户发起请求 listen(serv_sock, 20); //接收客户端请求 struct sockaddr_in clnt_addr; socklen_t clnt_addr_size = sizeof(clnt_addr); int clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size); //向客户端发送数据 char str[] = "Hello World"; write(clnt_sock, str, sizeof(str)); //关闭套接字 close(clnt_sock); close(serv_sock); return 0; }
客户端:
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> int main(){ //创建套接字 int sock = socket(AF_INET, SOCK_STREAM, 0); //向服务器(特定的IP和端口)发起请求 struct sockaddr_in serv_addr; memset(&serv_addr, 0, sizeof(serv_addr)); //每个字节都用0填充 serv_addr.sin_family = AF_INET; //使用IPv4地址 serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具体的IP地址 serv_addr.sin_port = htons(1234); //端口 connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); //读取服务器传回的数据 char buffer[40]; read(sock, buffer, sizeof(buffer)-1); printf("Message form server: %s\n", buffer); //关闭套接字 close(sock); return 0; }
windows系统下就比较麻烦了,需要预先加载Winsock.dll 或 ws2_32.dll文件,而且windows跟linux所用的一些函数功能虽然相同,但是名称却是不同的,这个要注意一下。
服务器:#include <stdio.h> #include <winsock2.h> #pragma comment (lib, "ws2_32.lib") //加载 ws2_32.dll int main(){ //初始化 DLL WSADATA wsaData; WSAStartup( MAKEWORD(2, 2), &wsaData); //创建套接字 SOCKET servSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); //绑定套接字 sockaddr_in sockAddr; memset(&sockAddr, 0, sizeof(sockAddr)); //每个字节都用0填充 sockAddr.sin_family = PF_INET; //使用IPv4地址 sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具体的IP地址 sockAddr.sin_port = htons(1234); //端口 bind(servSock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR)); //进入监听状态 listen(servSock, 20); //接收客户端请求 SOCKADDR clntAddr; int nSize = sizeof(SOCKADDR); SOCKET clntSock = accept(servSock, (SOCKADDR*)&clntAddr, &nSize); //向客户端发送数据 char *str = "Hello World!"; send(clntSock, str, strlen(str)+sizeof(char), NULL); //关闭套接字 closesocket(clntSock); closesocket(servSock); //终止 DLL 的使用 WSACleanup(); return 0; }
客户端:
#include <stdio.h> #include <stdlib.h> #include <WinSock2.h> #pragma comment(lib, "ws2_32.lib") //加载 ws2_32.dll int main(){ //初始化DLL WSADATA wsaData; WSAStartup(MAKEWORD(2, 2), &wsaData); //创建套接字 SOCKET sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); //向服务器发起请求 sockaddr_in sockAddr; memset(&sockAddr, 0, sizeof(sockAddr)); //每个字节都用0填充 sockAddr.sin_family = PF_INET; sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); sockAddr.sin_port = htons(1234); connect(sock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR)); //接收服务器传回的数据 char szBuffer[MAXBYTE] = {0}; recv(sock, szBuffer, MAXBYTE, NULL); //输出接收到的数据 printf("Message form server: %s\n", szBuffer); //关闭套接字 closesocket(sock); //终止使用 DLL WSACleanup(); system("pause"); return 0; }
0x05 socket()函数解析
socket()函数在两个平台下参数是相同的,不同的是返回值,大家可以参考上面的代码。
先说一下linux系统下的socket()函数。在 Linux系统下使用 <sys/socket.h> 头文件中 socket() 函数来创建套接字,原型为:int socket(int af, int type, int protocol);
af指的是地址族(Address Family),也就是ip地址类型。当然有时候也会使用pf,到时候见到就不用惊讶了。type指的是数据传输方式/套接字类型,我们在上面已经讲了,根据数据传输方式的不同,我们可以将socket划分为两种常用类型,这里指的就是这个。protocol 表示传输协议,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分别表示 TCP 传输协议和 UDP 传输协议。
一般情况下有了 af 和 type 两个参数就可以创建套接字了,操作系统会自动推演出协议类型,但是有时候有两种不同的协议支持同一种地址类型和数据传输类型,这个时候我们就需要指定传输协议了。
参数 af 的值为 PF_INET。如果使用 SOCK_STREAM 传输数据,那么满足这两个条件的协议只有 TCP,因此可以这样来调用 socket() 函数:int tcp_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
这种套接字称为 TCP 套接字。
如果使用 SOCK_DGRAM 传输方式,那么满足这两个条件的协议只有 UDP,因此可以这样来调用 socket() 函数:
int udp_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
这种套接字称为 UDP 套接字。
上面两种情况都只有一种协议满足条件,可以将 protocol 的值设为 0,系统会自动推演出应该使用什么协议,如下所示:
int tcp_socket = socket(AF_INET, SOCK_STREAM, 0);
int udp_socket = socket(AF_INET, SOCK_DGRAM, 0);windows平台下也是如此
SOCKET socket(int af, int type, int protocol);
-
Socket的学习(一)什么是Socket?
2018-06-27 21:53:01本文参考的是《Socket通信原理》https://www.cnblogs.com/wangcq/p/3520400.html TCP/IP UDP是什么? TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,是一个工业标准的协议...本文参考的是《Socket通信原理》https://www.cnblogs.com/wangcq/p/3520400.html
一、TCP/IP UDP是什么?
TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,是一个工业标准的协议集,它是为广域网(WANs)设计的。UDP(User Data Protocol,用户数据报协议)是与TCP相对应的协议。它是属于TCP/IP协议族中的一种。下面是他们三者的关系:
可以看出TCP/IP协议族包括运输层、网络层、链路层。socket是一个接口,在用户进程与TCP/IP协议之间充当中间人,完成TCP/IP协议的书写,用户只需理解接口即可。
二、socket与TCP/IP的对应关系
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
原作者将socket通信类比为打电话这一生活场景。这里我把TCP服务器比作政府某一服务部门能,TCP客户端比作企业中某一部门电话,描述这一过程,恰好就像是socket通信,服务部门提供服务,企业部门申请服务。
要实现通信,首先政府部门都必须申请一个电话(socket_fd),并向有关部门注册(我们的系统),提供地址(sockadrr)以及属于哪个部门的(port),录入系统后,就算是合约生效了(bind),于是乎,政府广而告之,这个服务热线就算开通了,在部门里面的人员所需要做的事情,就是等待企业家拨打热线(listen)。
企业家拨打电话对地点和部门没有这么多的要求了,他并不需要绑定地址和部门,在任何一个可以拨打电话的地方(可能是同个部门,也可以同公司不同部门,甚至可能是竞争对手),他只需要拿起一个已经注册的电话(socket_fd),拨打电话(connect)
政府部门接通电话(accept)后,桥梁就打通了(服务者client_fd、顾客server_fd),可以进行听说了(read write)。企业家咨询完成(close),政府到点下班关闭服务(close)三、socket API 简单介绍
3.1 socket() 创建socket描述符
int socket(int domain, int type, int protocol); //成功返回非负描述符,失败返回-1
domain:即协议域,又称为协议族(family)。
常用的地址族有:- AF_INET
- AF_INET6
- AF_LOCAL(AF_UNIX,本地通信用)
- AF_ROUTE
协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。
type:信息传送方式。
- SOCK_STREAM
- SOCK_DGRAM
- SOCK_RAW
- SOCK_PACKET
- SOCK_SEQPACKET
protocol:对应协议。
- IPPROTO_TCP TCP传输协议
- IPPROTO_UDP UDP传输协议
- IPPROTO_SCTP STCP传输协议
- IPPROTO_TIPCTIPC传输协议
通常设置为0,让其自动匹配。
我的理解就是:
domain 网络层相关协议 type 信息传送方式 protocol 运输层相关协议,如果前两个都确定了,系统就可以自动选择协议了。3.2 bind()绑定实际地址
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); //返回值:成功则为0,失败为-1
sockfd 一般服务端才需要绑定,客户端由系统内核解决
addr 所有协议都有一个公共的结构叫做const struct sockaddr
,不同协议对应不同的具体结构,结构如下:struct sockaddr { sa_family_t sin_family;//地址族 char sa_data[14]; //14字节,包含套接字中的目标地址和端口信息 };
可以看出,我们端口和目标地址放在同一数组中,不太容易使用。对于不同的协议,我们会通过另一种特定的结构体的来完成初始化,再通过强制类型转换,来使用bind函数。
强制转换第二个地址参数为const sockaddr *
(const sockaddr *)sockaddr_in; (const sockaddr *)sockaddr_in6; (const sockaddr *)sockaddr_un;
- ipv4结构体
struct sockaddr_in { sa_family_t sin_family; in_port_t sin_port; //typedef __uint16_t in_port_t; struct in_addr sin_addr; }; struct in_addr { uint32_t s_addr; };
- ipv6结构体
struct sockaddr_in6 { sa_family_t sin6_family; in_port_t sin6_port; uint32_t sin6_flowinfo; struct in6_addr sin6_addr; uint32_t sin6_scope_id; }; struct in6_addr { unsigned char s6_addr[16]; };
- Unix域结构体
#define UNIX_PATH_MAX 108 struct sockaddr_un { sa_family_t sun_family; char sun_path[UNIX_PATH_MAX]; };
- addrlen:协议结构体大小
通常使用sizeof运算符计算
sizeof(sockaddr_in);//IPv4 in:internet sizeof(sockaddr_in6);//IPv6 sizeof(sockaddr_un);//本地 un:unix
3.3 listen()、connect() 主机监听、从机链接
int listen(int sockfd, int backlog); //返回值:成功则为0,失败为-1
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); //返回值:成功则为0,失败为-1
listen决定需要开启的部门热线,connect拨打电话。
listen函数的第一个参数即为要监听的socket描述字,第二个参数为相应socket可以排队的最大连接个数。socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户的连接请求。
connect函数的第一个参数即为客户端的socket描述字,第二参数为服务器的socket地址,第三个参数为socket地址的长度。客户端通过调用connect函数来建立与TCP服务器的连接。
3.4 accept 建立链接
只要服务端建立链接,我们就可以进行操作了
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); //返回值:成功则为0,失败为-1
第一个参数为服务器的socket描述字,
第二个参数为指向struct sockaddr *的指针,用于返回客户端的协议地址
第三个参数为协议地址的长度。如果accpet成功,那么其返回值是由内核自动生成的一个全新的描述字,代表与返回客户的TCP连接。后面的读写操作都是根据这个返回值来完成的。
3.5 read和write函数,读写
这不是属于socket的API,但是socket总是伴随着以下函数:
ssize_t read(int fd, void *buf, size_t count); ssize_t write(int fd, const void *buf, size_t count); ssize_t send(int sockfd, const void *buf, size_t len, int flags); ssize_t recv(int sockfd, void *buf, size_t len, int flags); ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags); ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
read函数是负责从fd中读取内容.当读成功时,read返回实际所读的字节数,如果返回的值是0表示已经读到文件的结束了,小于0表示出现了错误。如果错误为EINTR说明读是由中断引起的,如果是ECONNREST表示网络连接出了问题。
write函数将buf中的nbytes字节内容写入文件描述符fd.成功时返回写的字节 数。失败时返回-1,并设置errno变量。在网络程序中,当我们向套接字文件描述符写时有俩种可能。1)write的返回值大于0,表示写了部分或者是 全部的数据。2)返回的值小于0,此时出现了错误。我们要根据错误类型来处理。如果错误为EINTR表示在写的时候出现了中断错误。如果为EPIPE表示 网络连接出现了问题(对方已经关闭了连接)。
3.6 close关闭服务
int close(int fd); //返回值:成功则为0,失败为-1
close一个TCP socket的缺省行为时把该socket标记为以关闭,然后立即返回到调用进程。该描述字不能再由调用进程使用,也就是说不能再作为read或write的第一个参数。
注意:close操作只是使相应socket描述字的引用计数-1,只有当引用计数为0的时候,才会触发TCP客户端向服务器发送终止连接请求。
-
socket编程套接字详解
2020-08-01 11:28:57一、理解socket socket即为套接字,在TCP/IP协议中,“IP地址+TCP或UDP端口号”唯一的标识网络通讯中的一个进程,“IP地址+TCP或UDP端口号”就为socket。 在TCP协议中,建立连接的两个进程(客户端和服务器)各自有...一、理解socket
- socket即为套接字,在TCP/IP协议中,“IP地址+TCP或UDP端口号”唯一的标识网络通讯中的一个进程,“IP地址+TCP或UDP端口号”就为socket。
- 在TCP协议中,建立连接的两个进程(客户端和服务器)各自有一个socket来标识,则这两个socket组成的socket pair就唯一标识一个连接。
- socket本身就有“插座”的意思,因此用来形容网络连接的一对一关系,为TCP/IP协议设计的应用层编程接口称为socket API。
二、网络字节序
内存中的多字节数据都有大小端之分,磁盘文件中的多字节数据相对于文件中的偏移地址也有大小端之分,同样,网络数据流也有大小端之分。
网络数据流的地址规定:先发出的数据时低地址,后发出的数据是高地址。发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出,为了不使数据流乱序,接收主机也会把从网络上接收的数据按内存地址从低到高的顺序保存在接收缓冲区中。
TCP/IP协议规定:网络数据流应采用大端字节序,即低地址高字节。
由于两端的两个主机的大小端不一定相同,因此为了使这些网络数据具有更强的可移植性,使相同的代码在大端和小端主机上都能正常运行,我们可以调用以下库函数进行网络字节序和主机字节序的相关转换:
#include<arpa/inet.h> //将主机字节序转换为网络字节序 uint32_t htonl(uint32_t hostlong);//将32长整数从主机字节序转换为网络字节序, //如果主机字节序是小端,则函数会做相应大小 //端转换后返回;如果主机字节序是大端,则函 //数不做转换,将参数原封不动返回。。。下同 uint16_t htons(uint16_t hostshort); //将网络字节序转换为主机字节序 uint32_t ntohl(uint32_t netlong); uint16_t ntohs(uint16_t netshort); // h表示主机(host),n表示网络(net),l表示32位长整数,s表示16短整数。
三、TCP协议通讯的实现
我们先介绍几个函数:
1、创建套接字int socket(int domain,int type,int protocol); //domain:该参数一般被设置为AF_INET,表示使用的是IPv4地址。还有更多选项可以利用man查看该函数 //type:该参数也有很多选项,例如SOCK_STREAM表示面向流的传输协议,SOCK_DGRAM表示数据报,我们这里实现的是TCP,因此选用SOCK_STREAM,如果实现UDP可选SOCK_DGRAM //protocol:协议类型,一般使用默认,设置为0
该函数用于打开一个网络通讯接口,出错则返回-1,成功返回一个socket(文件描述符),应用进程就可以像读写文件一样调用read/write在网络上收发数据。
2、绑定
int bind(int sockfd,const struct sockaddr*addr,socklen_t addrlen); //sockfd:服务器打开的sock //后两个参数可以参考第四部分的介绍
服务器所监听的网络地址和端口号一般是固定不变的,客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接,因此服务器需要调用bind来绑定一个固定的网络地址和端口号。bind成功返回0,出错返回-1。
bind()的作用:将参数sockfd和addr绑定在一起,是sockfd这个用于网络通讯的文件描述符监听addr所描述的地址和端口号。3、监听
int listen(int sockfd,int backlog); //sockfd的含义与bind中的相同。 //backlog参数解释为内核为次套接口排队的最大数量,这个大小一般为5~10,不宜太大(是为了防止SYN攻击)
该函数仅被服务器端使用,listen()声明sockfd处于监听状态,并且最多允许有backlog个客户端处于连接等待状态,如果收到更多的连接请求就忽略。listen()成功返回0,失败返回-1。
4、接收连接
int accept(int sockfd,struct sockaddr* addr,socklen_t* addrlen); //addrlen是一个传入传出型参数,传入的是调用者的缓冲区cliaddr的长度,以避免缓冲区溢出问题;传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区)。如果给cliaddr参数传NULL,表示不关心客户端的地址。
典型的服务器程序是可以同时服务多个客户端的,当有客户端发起连接时,服务器就调用accept()返回并接收这个连接,如果有大量客户端发起请求,服务器来不及处理,还没有accept的客户端就处于连接等待状态。
三次握手完成后,服务器调用accept()接收连接,如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来。5、请求连接
int connect(int sockfd,const struct sockaddr* addr,socklen_t addrlen);
这个函数只需要有客户端程序来调用,调用该函数后表明连接服务器,这里的参数都是对方的地址。connect()成功返回0,出错返回-1。
了解这些函数后,我们来捋一捋客户端程序和服务器程序建立连接的过程:
-
服务器:首先调用socket()创建一个套接字用来通讯,其次调用bind()进行绑定这个文件描述符,并调用listen()用来监听端口是否有客户端请求来,如果有,就调用accept()进行连接,否则就继续阻塞式等待直到有客户端连接上来。连接建立后就可以进行通信了。
-
客户端:调用socket()分配一个用来通讯的端口,接着就调用connect()发出SYN请求并处于阻塞等待服务器应答状态,服务器应答一个SYN-ACK分段,客户端收到后从connect()返回,同时应答一个ACK分段,服务器收到后从accept()返回,连接建立成功。客户端一般不调用bind()来绑定一个端口号,并不是不允许bind(),服务器也不是必须要bind()。
为什么不建议客户端进行bind()?
答:当客户端没有自己进行bind时,系统随机分配给客户端一个端口号,并且在分配的时候,操作系统会做到不与现有的端口号发生冲突。但如果自己进行bind,客户端程序就很容易出现问题,假设在一个PC机上开启多个客户端进程,如果是用户自己绑定了端口号,必然会造成端口冲突,影响通信。进行一番理论知识后我们就可以写代码了:
“server.c”#include<stdio.h> #include<sys/types.h> #include<sys/socket.h> #include<stdlib.h> #include<unistd.h> #include <netinet/in.h> #include <arpa/inet.h> int startup(int _port,const char* _ip) { int sock = socket(AF_INET,SOCK_STREAM,0); if(sock < 0) { perror("socket"); exit(1); } struct sockaddr_in local; local.sin_family = AF_INET; local.sin_port = htons( _port); local.sin_addr.s_addr = inet_addr(_ip); socklen_t len = sizeof(local); if(bind(sock,(struct sockaddr*)&local , len) < 0) { perror("bind"); exit(2); } if(listen(sock, 5) < 0) //允许连接的最大数量为5 { perror("listen"); exit(3); } return sock; } int main(int argc,const char* argv[]) { if(argc != 3) { printf("Usage:%s [loacl_ip] [loacl_port]\n",argv[0]); return 1; } int listen_sock = startup(atoi(argv[2]),argv[1]);//初始化 //用来接收客户端的socket地址结构体 struct sockaddr_in remote; socklen_t len = sizeof(struct sockaddr_in); while(1) { int sock = accept(listen_sock, (struct sockaddr*)&remote, &len); if(sock < 0) { perror("accept"); continue; } printf("get a client, ip:%s, port:%d\n",inet_ntoa(remote.sin_addr),ntohs(remote.sin_port)); char buf[1024]; while(1) { ssize_t _s = read(sock, buf, sizeof(buf)-1); if(_s > 0) { buf[_s] = 0; printf("client:%s",buf); } else { printf("client is quit!\n"); break; } } } return 0; }
”client.c“
#include<stdio.h> #include<sys/types.h> #include<sys/socket.h> #include<unistd.h> #include<stdlib.h> #include<netinet/in.h> #include<arpa/inet.h> int main(int argc,const char* argv[]) { if(argc != 3) { printf("Usage:%s [ip] [port]\n",argv[0]); return 0; } //创建一个用来通讯的socket int sock = socket(AF_INET,SOCK_STREAM, 0); if(sock < 0) { perror("socket"); return 1; } //需要connect的是对端的地址,因此这里定义服务器端的地址结构体 struct sockaddr_in server; server.sin_family = AF_INET; server.sin_port = htons(atoi(argv[2])); server.sin_addr.s_addr = inet_addr(argv[1]); socklen_t len = sizeof(struct sockaddr_in); if(connect(sock, (struct sockaddr*)&server, len) < 0 ) { perror("connect"); return 2; } //连接成功进行收数据 char buf[1024]; while(1) { printf("send###"); fflush(stdout); ssize_t _s = read(0, buf, sizeof(buf)-1); buf[_s] = 0; write(sock, buf, _s); } close(sock); return 0; }
但是这样实现只能进行单进程通信,也就是说每次只能使一个客户端连接上进行数据通讯,这显然不符合服务器的基本要求。我们可以想办法修改服务器端的代码,每次accept成功之后就创建一个子进程,让子进程去处理读写数据,父进程继续监听并accept。
具体代码:https://github.com/lybb/Linux/tree/master/TCP_pro修改的代码在服务器程序的socket()和bind()之间加入了如下的代码:
int opt=1; setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)); //设置sockfd的选项为SO_REUSEADDR为1,表示允许创建端口号相同但IP不同的多个 //socket描述符
但是如果是用创建子进程的方法比较浪费资源,我们可以修改为创建线程的方法
四、sockaddr数据结构
IPv4 和 IPv6 的地址格式定义在“netinet/in.h”中,IPv4用sockaddr_in结构体表示,包括16位端口号和32位IP地址;IPv6用sockaddr_in6结构体表示,包括16位端口号、128位IP地址和一些控制字段。
UNIX Domain Socket的地址格式定义在sys/un.h中,用sockaddr_un结构体表示。
各种socket地址结构体的开头都是相同的,前16位表⽰示整个结构体的长度(并不是所有UNIX的实现都有长度字段,如Linux就没有),后16位表示地址类型。IPv4、IPv6和UNIX Domain Socket的地 址类型分别定义为常数AF_INET、AF_INET6、AF_UNIX。
这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容。
因此socket API可以接受各种类型的sockaddr结构体指针做参数,例 如bind、accept、connect等函数,这些函数的参数应该设计成void 类型以便接受各种类型的指针,但是sock API的实现早于ANSI C标准化,那时还没有void 类型,因此这写函数的参数都用struct sockaddr*类型表示,在传参之前需要强制类型转换(在bind函数中就有用到)。
-
socket原理及实现
2021-03-24 17:23:31常用的socket类型有,SOCK_STREAM(面向连接可靠方式,比如TCP)、SOCK_DGRAM(非面向连接的非可靠方式,比如UDP)、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等(socket的类型有哪些?)。 protocol:故名思意,... -
socket通信简述
2021-08-26 16:52:04对TCP/IP、UDP、Socket编程这些词你不会很陌生吧?随着网络技术的发展,这些词充斥着我们的耳朵。那么我想问: 1.什么是TCP/IP、UDP? 2.Socket在哪里呢? 3.Socket是什么呢? 4.你会使用它们吗?什么是TCP/IP、UDP... -
java Socket
2021-02-28 08:59:39通常大家会用以下方法进行进行结束: 调用socket.close() 或者socket.shutdownOutput()方法。 调用这俩个方法,都会结束客户端socket。但是有本质的区别。 socket.close() 将socket关闭连接,那边如果有服务端给... -
WIFI学习一(socket介绍)
2022-04-10 15:51:15什么是socket socket译为“插座”,在计算机通信领域,socket被翻译为“套接字”,它是计算机之间进行通信的一种约定或一种方式。通过这种方式,一台计算机可以接受其他计算机的数据,也可以向其他计算机发送数据。... -
LwIP使用select,close socket资源释放不完全问题
2021-05-18 06:21:26/* @todo: check this */ /* create a netconn */ /* 下面主要是针对不同的socket类型,分配空间,对相应的成员进行赋值,空间资源为预分配给lwip的堆空间 */ switch (type) { case SOCK_RAW: conn = netconn_new_... -
套接字有哪些类型?socket有哪些类型?
2019-06-26 14:26:19这个世界上有很多种套接字(socket),比如 DARPA Internet 地址(Internet 套接字)、本地节点的路径名(Unix套接字)、CCITT X.25地址(X.25 套接字)等。但本教程只讲第一种套接字——Internet 套接字,它是最具... -
Java socket详解
2020-12-31 21:25:18如上图,在七个层级关系中,我们将的socket属于传输层,其中UDP是一种面向无连接的传输层协议。UDP不关心对端是否真正收到了传送过去的数据。如果需要检查对端是否收到分组数据包,或者对端是否连接到网络,则需要在... -
linux网络socket
2022-03-10 21:04:37文章目录理解源ip地址和目的ip地址端口号端口号和pid套接字网络字节序socket编程接口sockaddr结构 理解源ip地址和目的ip地址 在IP数据包头部中, 有两个IP地址, 分别叫做源IP地址, 和目的IP地址 ip地址可以确定唯一一... -
Socket(套接字)简介
2020-05-18 23:52:32要想理解socket首先得熟悉一下TCP/IP协议族, TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,定义了主机如何连入因特网及数据如何再它们之间传输的标准, 从字面意思来看TCP/... -
Python Socket介绍
2019-12-12 20:36:23Socket并不属于TCP/IP协议簇,它只是一个编程接口,即对TCP/IP的封装和应用,简单理解TCP/IP看看作一个函数,而Socket用来进行调用,Socket可在网络中对两个程序建立通信通道,Socket可分为两个基本模块,一个服务端... -
Socket 通信原理
2019-07-30 18:19:081. 客户端socket发送消息后,为什么服务端socket没有收到? 2. 使用while 循环实现连续输入,是不是就是多线程模式? 3. 对多线程处理机制不是很明白,希望详细讲解? 4. 希望详细讲解ServerSocketChannel和... -
Socket编程概念和 Socket之异步TCP客户端断线重连
2021-07-27 21:38:05一:什么是SOCKET socket的英文原义是“孔”或“插座”。作为进程通信机制,取后一种意思。通常也称作“套接字”,用于描述IP地址和端口,是一个通信链的句柄(其实就是两个程序通信用的)。 socket非常类似于电话... -
【Socket】Java Socket编程基础及深入讲解
2021-12-12 15:19:14Socket是Java网络编程的基础,了解还是有好处的, 这篇文章主要讲解Socket的基础编程。Socket用在哪呢,主要用在进程间,网络间通信。本篇比较长,特别做了个目录: 一、Socket通信基本示例 二、消息通信优化 ... -
基于UDP的Socket通信
2020-06-08 22:24:41基于UDP的Socket通信 注意,在使用UDP实现Socket通信时一定要使用两台真机,不要使用虚拟机,不然会出现UDP包无法发送的情况。 UDP ( User Datagram Protocol,用户数据报协议)是-种面向无连接的传输层协议,提供不... -
RAW socket使用
2021-06-09 14:12:55首先讨论在使用以下任何API进行开发时必须考虑的一些一般性问题。除了这里引出的问题外,编程者还必须注意他想要使用的网络协议的特殊性。如果没有注意到这种特殊性,例如交换信息的正确顺序,就不能确保我们能够与... -
1.1 利用Socket实现双机通信
2020-03-29 17:29:46利用Socket实现双机通信目的环境所需知识实验分析实验结果代码 目的 利用WinSock来实现双机通信,理解TCP状态图 要求使用WinSock编程,采用其中的TCP面向连接方式,实现文本数据的交换。 环境 Windows10操作系统、... -
那些年,我们忽略的socket参数
2021-05-15 21:09:05调试过网络程序的人大多使用过tcpdump,那你知道tcpdump是如何工作的...本文不分析tcpdump的具体实现,而只是借tcpdump来揭示一些网络编程中一个大多数人都容易忽略的一个主题:Socket参数对用户接收报文的影响...相... -
Java socket详解,看这一篇就够了
2021-07-21 20:39:02刚给大家讲解Java socket通信后,好多童鞋私信我,有好多地方不理解,看不明白。特抽时间整理一下,详细讲述Java socket通信原理和实现案例。整个过程楼主都是通过先简单明了的示例让大家了解整个基本原理,后慢慢... -
网络编程Socket、Netty
2020-07-23 09:55:11以下三层多体现在硬件层面上 网络层:为我们的数据实现路由(路由器、交换机) 数据链路层:传输的地址的帧及错误的检测 物理层:以二进制形式,在物理机上实现数据传输(光纤、专线、各种物理介质实现) 网络编程 ... -
Linux netlink socket使用总结
2018-09-21 10:43:04netlink是基于socket的通信机制,由于socket本身的双工性、突发性、不阻塞性等特点,能够很好地满足内核空间与用户空间小量数据的及时交互,因此在Linux 2.6内核开始被广泛使用,例如内核态的netfilter与用户态的... -
socket从userspace到kernel的api执行过程(不含tcp/ip协议栈部分)
2016-11-29 17:33:01glibc版本2.3.6。Kernel版本:4.3 Userspace glibc接口说明 glibc中socket接口定义:(glibc-x.x.x/sysdeps/generic/socket.cx.x.x是版本号) int __socket (domain, type, protocol) int domain; int -
Linux下实现socket网络通信(一)
2021-05-06 19:32:43目录socket网络编程1、服务端的工作流程2、客户端工作流程相关库函数和注意事项头文件1、socket文件描述符2.1、把服务端用于通信的地址和端口绑定到socket1、主机字节序与网络字节序2、ip地址转为网络字节序3、转换... -
Socket详解
2012-11-01 22:41:11“一切皆Socket!” 话虽些许夸张,但是事实也是,现在的网络编程几乎都是用的socket。 ——有感于实际编程和开源项目研究。 我们深谙信息交流的价值,那网络中进程之间如何通信,如我们...socket的类型有哪些?还有