大家好,又见面了,我是你们的朋友全栈君。
网络编程入门
前言
“任何职业都可以成为黑客。你可以是一个木匠黑客。不一定是高科技。只要与技能有关,并且倾心专注于你正在做的事情,你就可能成为黑客。” ——引自《黑客伦理与信息时代精神》
在这里引入黑客定义就是想提醒自己和大家,真正的黑客精神:Open,Free,Share,热衷共享最新研究。时刻谨记黑客精神,不要成为现在盛行的”Script Kid”(专指只会使用现成软件进行攻击以取得满足感的,危害网络空间安全的人)。希望大家都能够为网络环境贡献自己的一份力量。
本人是刚开始学习黑客编程的小白,在此做笔记,有误之处,请大牛们指出。
网络编程入门是踏入黑客神圣殿堂的第一课,掌握好网络编程将会帮助你更好的获得对目标计算机的控制。我个人使用的编程环境是CodeBlocks。这里网络编程省去了 Netapi 编程,因为我个人没有运行起来,而且只能在一个局域网内,所以在真正的网络木马病毒中并不常见,所以在此略去。转而使用 Socket 编程。Socket 编程需要使用的库函数对于不同的编译环境也是不同的。如果编译使用的是MINGW,那么需要用的头文件是 winsock2.h ,静态链接库是 libws2_32.a。如果是在 CodeBlocks 中的话,如下图:
以后也是这样子的操作,需要什么静态或者动态链接库就可以手动在这里添加。这里我也顺便说一下DevC++的添加方式。首先要新建工程,本人比较菜,暂时只知道这种方法。如下图:
对于VC++的话,课本上给出的方式:
#pragma comment(lib, "Ws2_32")
但是我没成功过,emmm,就转而使用CodeBlocks了。
Socket技术详细介绍
个人建议了解一下原理,推荐这篇博客。
Socket 编程
所需函数
- WSAStartup函数
int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
参数 | 含义 |
---|---|
wVersionRequested | 指明程序请求使用的Socket版本,高位字节指明副版本,低字节指明主版本 |
lpWSAData | 指向WSADATA的指针,返回系统Socket的版本信息 |
在使用 Socket 的程序之前必须调用该函数,初始化 Windows Socket DLL,否则后面的任何函数都将调用失败。调用该函数时,操作系统根据请求的 Socket 版本来搜索相应的库,然后绑定找到的库到该应用程序中,以后应用程序就可以调用所请求的库中的函数了。该函数执行成功则返回0。
-
WSACleanup函数
int WSACleanup(void)
应用程序在完成对请求的 Socket 库的使用后,用此函数来解除与 Socket 库的绑定且释放所占用的系统资源。 -
socket函数
SOCKET socket(int af, int type, int protocol)
参数 | 含义 |
---|---|
af | 指定应用程序使用的通信协议的协议族,对于TCP/IP协议族,该参数置PF_INET或AF_INET。两者在Windows下是相同的 |
type | 指定套接字的类型,刘套接字类型为SOCK_STREAM,数据报套接字类型为SOCK_DGRAM |
protocol | 应用程序所使用的通信协议,TCP置IPPROTO_TCP |
创建成功返回 SOCKET 类型的套接字描述符,失败就返回 INVALID_SOCKET 。
其实还有一个WSASocket函数,不过这里先不做介绍,先留一个小尾巴,等以后会提到。
-
closesocket函数
int closecocket(SOCKET s)
每个进程都有一个套接字描述符表,表中的每个套接字描述符都对应了一个位于操作系统缓冲区中的套接字数据结构,因此有可能有几个套接字描述符指向同一个套接字数据结构。套接字数据结构中专门有一个字段存放该结构被引用次数。执行该函数,先检查引用次数,如果为1,则把s从表象中移除,并且释放对应的套接字数据结构;如果大于1,仅清除s在套接字描述符的对应表项,引用次数减1。
执行成功就返回0,否则返回 SOCKET_ERROR 。 -
send函数
int send(SOCKET s, const char FAR *buf, int len, int flags)
参数 | 含义 |
---|---|
s | 指定发送端的套接字描述符 |
buf | 指明一个存放应用进程发送数据的缓冲区 |
len | 指明实际要发送的数据的字节数 |
flags | 一般置0 |
注意:该函数只是将缓冲区中的数据复制到套接字发送缓冲区的剩余空间中,而不负责发送,至于数据什么时候发送由下层选用的协议决定。
发送成功则返回实际复制到发送缓冲区的字节数,如果复制过程或者链接断开都返回SOCKET_ERROR。
- sendto函数
int sendto(SOCKET s, const char * buf, int len, int flags, const struct socketaddr * to,int tolen)
参数 | 含义 |
---|---|
s | 指定发送端的套接字描述符 |
buf | 指明一个存放应用进程发送数据的缓冲区 |
len | 指明实际要发送的数据的字节数 |
flags | 一般置0 |
to | 指向目的地址的指针 |
tolen | to的长度 |
用于无连接的UDP协议,因为没有事先建立链接,所以需要指定源地址。
- recv函数
int recv(SOCKET s, char FAR *buf, int len, int flags)
参数 | 含义 |
---|---|
s | 指定接收端的套接字描述符 |
buf | 指明一个存放应用进程接收数据的缓冲区 |
len | 指明buf的长度 |
flags | 一般置0 |
与send类似,也是用于面向连接的服务,如果成功则返回实际从接收缓冲区复制的字节数,如果从 socket 的接收队列向 buf 复制的过程中出错则返回SOCKET_ERROR,如果网络连接中断返回0。
- recvfrom函数
int recvfrom(SOCKET s, const char * buf, int len, int flags, const struct socketaddr * from,int fromlen)
参数 | 含义 |
---|---|
s | 指定接收端的套接字描述符 |
buf | 指明一个存放应用进程接收数据的缓冲区 |
len | 指明buf的长度 |
flags | 一般置0 |
from | 指向源地址地址的指针 |
fromlen | from的长度 |
- bind函数
int bind(SOCKET s, const struct sockaddr FAR* name, int namelen)
参数 | 含义 |
---|---|
s | 指定待绑定的套接字描述符 |
name | 指明带绑定的IP和端口等信息组合而成的结构体 |
namelen | 指明name长度 |
// sockaddr 结构体的定义
struct sockaddr {
u_short sa_family;
char sa_data[14];
}
// 在实际绑定的时候我们通常使用另一个地址结构,用强制类型转换使类型一致
struct sockaddr_in {
short sin_family; // 指定协议族
u_short sin_port; // 指定端口号,一般用htons()函数进行类型转换
struct in_addr sin_addr; // 该结构体中的 s_addr 指定要绑定的IP地址
//通常用inet_addr()函数对IP地址字符串类型进行转换
char sin_zero[8]; //
}
- listen函数
int listen(SOCKET s, int backlog)
参数 | 含义 |
---|---|
s | 指定待监听的套接字描述符 |
backlog | 监听队列中最多容纳的客户连接数 |
执行成功返回0,否则返回SOCKET_ERROR
- accept函数
SOCKET accept(SOCKET s, struct sockaddr FAR *addr, int FAR *addrlen)
参数 | 含义 |
---|---|
s | 指定处于监听状态的套接字描述符 |
addr | 返回新创建的套接字的地址结构 |
addr | 新创建的套接字地址结构的长度 |
服务程序调用accept函数从处于监听状态的流套接字s的客户请求队列中取出排在最前的一个客户请求,并创建一个新的套接字来与客户套接字创建连接通道,如果连接成功,就返回新创建的套接字描述符,以后与客户端套接字交换数据的是细腻建的套接字;如果失败就返回INVALID_SOCKET。
- connect函数
int connect(SOCKET s, const struct sockaddr FAR *name, int namelen)
参数 | 含义 |
---|---|
s | 指定客户端套接字描述符 |
name | 指定服务器地址 |
namelen | name的长度 |
如果连接成功,返回0;否则返回SOCKET_ERROR。
到这里为止可算是基本把所有会用到的函数都列举出来了,其实更常用的还是 TCP ,列出 UDP 使用的函数也是以防有这方面的需要。还有一个需要注意的地方就是主机字节顺序和网络字节顺序。我们使用的计算机大部分都是小端方式,而网络字节顺序都是大端方式,所以这就涉及到在发送数据时字节顺序的转换。比如上面说到的地址结构中的端口,在赋值的时候,我们使用 htons() 函数进行转换就是为了将我们的整型数字转换为大端方式。还有一个我们所需要的函数就是 inet_addr()。是将一个 IP 地址如“127.1.2.3”转换成一个32整数。如例子,转化完的值为0x0302017f。每两位16进制数代表了 IP 地址中的一位。
现在前置知识都已经有了,就可以开始编程了,当然推荐的博客里面的程序可以,也可以看一下代码,都是相似的。
服务器/客户 代码
服务器端代码:
#include <stdio.h>
#include <winsock2.h>
#include <windows.h>
unsigned long GetLocalIP() {
char szLocalIP[20] = {
0};
char szHostName[128+1] = "\0";
hostent *phe;
if ( gethostname(szHostName, 128) == 0 ) {
// Get host addresses
phe = gethostbyname(szHostName);
for ( int i = 0; phe != NULL && phe->h_addr_list[i] != NULL; i++ ) {
sprintf(szLocalIP, "%d.%d.%d.%d",
(UINT)((UCHAR*)phe->h_addr_list[i])[0],
(UINT)((UCHAR*)phe->h_addr_list[i])[1],
(UINT)((UCHAR*)phe->h_addr_list[i])[2],
(UINT)((UCHAR*)phe->h_addr_list[i])[3]
);
}
} else return 0;
printf("host name: %s\nIP: %s\n", szHostName, szLocalIP);
return inet_addr(szLocalIP);
}
int main() {
int sockfd, new_fd;
struct sockaddr_in my_addr;
struct sockaddr_in their_addr;
int sin_size;
// 初始化 Windows Socket Dll ,必须放在所有 Socket 编程之前
WSADATA ws;
WSAStartup( MAKEWORD(2, 2), // 初始化版本为2.2
&ws ); // 返回版本信息 ws 中
GetLocalIP();
sockfd = socket( AF_INET, // 建立 socket
SOCK_STREAM, // TCP/IP 协议族
0 ); // 套接字类型,通信协议设为0
// 绑定本机的830端口
my_addr.sin_family = AF_INET; // 协议类型是 INET
my_addr.sin_port = htons(830); // 绑定 830 端口
my_addr.sin_addr.s_addr = INADDR_ANY; // 本机任意 IP INADDR_ANY
bind(sockfd, // 绑定到 sockfd 上
(struct sockaddr*)&my_addr, // 地址结构指针
sizeof(struct sockaddr)); // 地址结构大小
listen(sockfd, // 在 sockfd 上监听
5); // 设定可等待客户队列的大小为5
printf("listening......\n");
sin_size = sizeof(struct sockaddr_in);
new_fd = accept(sockfd,
(struct sockaddr*)&their_addr,
&sin_size);
char their_addr_str[20];
sprintf(their_addr_str, "%d.%d.%d.%d",
their_addr.sin_addr.s_addr&0xff,
(their_addr.sin_addr.s_addr>>8)&0xff,
(their_addr.sin_addr.s_addr>>16)&0xff,
(their_addr.sin_addr.s_addr>>24)&0xff);
printf("Connected! Client IP: %s:%d\n", their_addr_str, their_addr.sin_port);
send(new_fd,
"ww0830\n",
14,
0);
printf("send OK!\n");
closesocket(sockfd);
closesocket(new_fd);
return 0;
}
客户端代码:
#include <stdio.h>
#include <winsock2.h>
#include <windows.h>
#define MAXDATASIZE 100
int main(int argc, char* argv[]) {
int sockfd, numbytes;
char buff[MAXDATASIZE];
struct sockaddr_in their_addr; // 对方的地址端口信息
if ( argc != 2 ) {
// 需要有服务端 ip 参数
fprintf(stderr, "usage: client hsotname\n");
exit(1);
}
WSADATA ws;
WSAStartup(MAKEWORD(2, 2), &ws); // 初始化 Windows Socket Dll
sockfd = socket(AF_INET, SOCK_STREAM, 0);
// 连接对方
their_addr.sin_family = AF_INET;
their_addr.sin_port = htons(830);
their_addr.sin_addr.s_addr = inet_addr(argv[1]);
// 连接服务器端
connect(sockfd, (struct sockaddr*)&their_addr, sizeof(struct sockaddr));
// 接受数据并打印
numbytes = recv(sockfd, buff, MAXDATASIZE, 0);
buff[numbytes] = '\0';
printf("Received: %s\n", buff);
closesocket(sockfd);
return 0;
}
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/150208.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...