#include "win/winsock2.bi" ' CONSTants CONST Socket_Buffer_Size = 2048 ' Socket Send/Receive buffer size CONST Socket_Buffer_Get = 255 ' How many bytes to get per loop CONST Socket_Is_Closed = 0 ' Socket is closed, or not used. CONST Socket_Is_Open = 1 ' Socket is open. CONST Socket_Is_Listening = 2 ' Socket is listening. CONST Socket_Is_Connecting = 3 ' Socket is connecting. CONST Socket_Is_Connected = 4 ' Socket is connected. CONST Socket_Is_Closing = 5 ' Socket is closing connection. CONST Socket_Is_Error = 6 ' Socket encountered an error. ' Types TYPE Our_Socket_Type sSocket AS SOCKET ' Winsock assigns its own SOCKET data type for sSocket. State AS UBYTE ' This is the status. SendBuffer AS ZSTRING * Socket_Buffer_Size ' 2k is sufficient enough. SendBytes AS ULONG ' The amount of bytes to send. RecvBuffer AS ZSTRING * Socket_Buffer_Size ' 2k is sufficient enough. RecvBytes AS ULONG ' The amount of bytes to receive. END TYPE ' We'll use 3 sockets in our chat program. CONST Max_Sockets = 3 DIM SHARED Our_Sockets(1 TO Max_Sockets) AS Our_Socket_Type ' Our sockets' handles. DIM SHARED Listen_Socket AS UBYTE, Main_Socket AS UBYTE, HTTP_Socket AS UBYTE, PORT AS UINTEGER ' Socket handles used in Connect 4 FB. DIM SHARED Workpage AS INTEGER, Polling AS INTEGER DECLARE SUB PrintErrorEnd (ErrorMessage$) ' Winsock specific calls. DECLARE SUB Winsock_Close () DECLARE SUB Winsock_Start () DECLARE FUNCTION Make_Socket_Accept (BYREF This_Socket AS UBYTE, BYREF Second_Socket AS UBYTE) AS SOCKET ' Accept incoming connection. DECLARE FUNCTION Make_Socket_Bind (BYREF This_Socket AS UBYTE, BYVAL PORT AS INTEGER) AS INTEGER ' Bind the socket to a particular port. DECLARE FUNCTION Make_Socket_Close (BYREF This_Socket AS UBYTE) AS BYTE ' Close the socket. DECLARE FUNCTION Make_Socket_Connect (BYREF This_Socket AS UBYTE, BYVAL stHostName AS STRING, BYVAL PORT AS INTEGER) AS BYTE DECLARE FUNCTION Make_Socket_Listen (BYREF This_Socket AS UBYTE, BYVAL TimeOut AS INTEGER = SOMAXCONN) AS BYTE DECLARE FUNCTION Make_Socket_Open (BYREF This_Socket AS UBYTE) AS BYTE ' Open up a socket. DECLARE FUNCTION Make_Socket_Receive (BYREF This_Socket AS UBYTE) AS INTEGER ' Get stuff from remote user. DECLARE FUNCTION Make_Socket_Resolve (BYVAL stHostName AS STRING) AS INTEGER DECLARE FUNCTION Make_Socket_Send (BYREF This_Socket AS UBYTE) AS INTEGER DECLARE FUNCTION Poll_Our_Sockets () DECLARE FUNCTION Socket_Index (BYREF sSocket AS SOCKET) AS BYTE ' Find Socket's UID by sSocket's ID DECLARE FUNCTION Socket_New () ' Finds an unused socket to use. DIM SHARED DummyStringA$, WinsockVersion$ ' Custom string routines. DIM SHARED Key$, InsertKey ' Keyboard input stuff for later on. ' Randomly selected port number to use for listening and/or connecting. PORT = 49952 ' 640 x 480 SCREEN 18, 8, 2 Winsock_Start DummyStringA$ = STR(Make_Socket_Open(Main_Socket)) DummyStringA$ += STR(Make_Socket_Bind(Main_Socket, PORT)) DummyStringA$ += STR(Make_Socket_Listen(Main_Socket, 0)) DO ' ======= MAIN LOOP ' Update screen. SCREENSET Workpage, Workpage XOR 1 CLS ' Animate some of our stuff, and use this as the frame counter/index. FrameAnimate = (FrameAnimate + 1) AND 255 ' Socket must always poll! Polling = Poll_Our_Sockets ' Keyboard Key$ = INKEY ' Grab our key from keyboard. IF Key$ = CHR(255) + CHR(82) THEN InsertKey = InsertKey XOR 1 ' Toggle insert key state. ' Print version. LOCATE 1, 25: PRINT "Your version of Winsock is " + WinsockVersion$ ' Print Polling status. LOCATE 26, 5: PRINT DummyStringA$ ' Flip work page. WorkPage XOR = 1 SLEEP 5 LOOP UNTIL Key$ = CHR(27) OR Key$ = CHR(255) + "k" ' == END OF MAIN LOOP ' Close Winsock and end the program! Winsock_Close FUNCTION Make_Socket_Accept (BYREF This_Socket AS UBYTE, BYREF Second_Socket AS UBYTE) AS SOCKET ' Socket address and length. DIM sa AS sockaddr_in DIM salen AS INTEGER ' Get a new socket reserved for our accepted connection. Second_Socket = Socket_New IF Second_Socket = -1 THEN ' No more sockets available. Cannot accept connection Make_Socket_Accept = Make_Socket_Close(This_Socket) EXIT FUNCTION END IF ' Get length salen = LEN(sa) ' Accept any incoming connections and use our new socket for it. Our_Sockets(Second_Socket).sSocket = Accept(Our_Sockets(This_Socket).sSocket, CAST(PSOCKADDR, @sa), @salen) IF Our_Sockets(Second_Socket).sSocket > 0 THEN ' Succeeded in accepting connection Our_Sockets(Second_Socket).State = Socket_Is_Connected END IF END FUNCTION FUNCTION Make_Socket_Bind (BYREF This_Socket AS UBYTE, BYVAL PORT AS INTEGER) AS INTEGER ' Socket address header DIM sa AS sockaddr_in sa.sin_port = htons(PORT) ' Convert the byte order for PORT. sa.sin_family = AF_INET ' IPv4 sa.sin_addr.S_addr = INADDR_ANY ' Any address format. ' Here, we associate a socket with our port. Make_Socket_Bind = Bind(Our_Sockets(This_Socket).sSocket, CAST(PSOCKADDR, @sa), LEN(sa)) <> SOCKET_ERROR END FUNCTION FUNCTION Make_Socket_Close (BYREF This_Socket AS UBYTE) AS BYTE ' Winsock's routine to close down both incoming and outgoing networking operations. ShutDown Our_Sockets(This_Socket).sSocket, SD_BOTH ' ow disconnect the handle and check to see if it's closed completely. IF CloseSocket(Our_Sockets(This_Socket).sSocket) = 0 THEN Our_Sockets(This_Socket).State = Socket_Is_Closed ' Closed socket This_Socket = 0 ' Unassign socket Make_Socket_Close = -1 ' Return true for successful close down. ELSE Our_Sockets(This_Socket).State = Socket_Is_Error ' Failed to close socket Make_Socket_Close = 0 END IF END FUNCTION FUNCTION Make_Socket_Connect (BYREF This_Socket AS UBYTE, BYVAL stHostName AS STRING, BYVAL PORT AS INTEGER) AS BYTE ' Socket address and IP DIM sa AS sockaddr_in DIM iIP AS INTEGER ' Check IF Socket is in Open State IF Our_Sockets(This_Socket).State <> Socket_Is_Open THEN EXIT FUNCTION ' Resolve address iIP = Make_Socket_Resolve(stHostName) ' Socket address header sa.sin_port = htons(PORT) ' htons changes the byte format. Port number gets associated. sa.sin_family = AF_INET ' IPv4 address as opposed to IPv6 (AF_INET6) sa.sin_addr.S_addr = iIP ' Our resolved IP number. ' This is Winsock's connect routine. IF Connect(Our_Sockets(This_Socket).sSocket, CAST(PSOCKADDR, @sa), LEN(sa)) <> SOCKET_ERROR THEN Our_Sockets(This_Socket).State = Socket_Is_Connecting ' Socket connecting Make_Socket_Connect = -1 ELSE IF WSAGetLastError = WSAEWOULDBLOCK THEN ' Normal, blocking mode is on. Our_Sockets(This_Socket).State = Socket_Is_Connecting ' Socket connecting Make_Socket_Connect = -1 ELSE Our_Sockets(This_Socket).State = Socket_Is_Error ' Failed to connect END IF END IF END FUNCTION FUNCTION Make_Socket_Listen (BYREF This_Socket AS UBYTE, BYVAL TimeOut AS INTEGER = SOMAXCONN) AS BYTE ' Winsock's Listen() routine doesn't need a non-zero number for TimeOut since we're in non-blocking mode. IF Listen(Our_Sockets(This_Socket).sSocket, TimeOut) <> SOCKET_ERROR THEN Our_Sockets(This_Socket).State = Socket_Is_Listening Make_Socket_Listen = -1 END IF END FUNCTION FUNCTION Make_Socket_Open (BYREF This_Socket AS UBYTE) AS BYTE ' Set Non-blocking mode on. DIM NonBlocking AS LONG NonBlocking = 1 ' Get unused Socket from our routine! This_Socket = Socket_New IF This_Socket = -1 THEN EXIT FUNCTION ' No more sockets available! ' Winsock's open socket routine Our_Sockets(This_Socket).sSocket = OpenSocket(AF_INET, SOCK_STREAM, IPPROTO_TCP) /' Winsock's socket protocol parameters: This is what we'll use. AF_INET (As opposed to AF_INET6) Since we're using IPv4 instead of IPv6 for IP addresses. SOCK_STREAM (As opposed to SOCK_DGRAM) Since we're using a connection-based socket, we use this. IPPROTO_TCP (As opposed to IPPROTO_UDP) TCP is slower than UDP, but ensures reliability and delivers data in the correct order. The first parameter is the address family. We'll be using IPv4. While IPv6 is slowly catching up (as of now), IPv4 is still the standard. It's also easier to type since it only goes up to 15 characters with decimal numbers. The second parameter is simply telling it we're using a stream (constant connection, kinda like a live phone connection) that will be used for TCP. For chatting, we're using TCP protocol. TCP is a connection-based real time protocol. Unlike UDP (Datagram), TCP is designed to make sure you receive packets (chunks of data), and in the correct order they were sent out. UDP (Datagram) is faster, doesn't need a constant streaming connection, however, it doesn't check to see if you received your packets in order, or if you even got the packets at all! For this reason, TCP is much easier to work with since it's designed to be reliable. '/ IF Our_Sockets(This_Socket).sSocket = NULL THEN EXIT FUNCTION /' The ioctlsocket function controls the I/O mode of a socket, which you will put your sockets into non-blocking mode. The FIONBIO and @lNonBlocking mode is basically telling FIONBIO (non-blocking command) to equal to 1, meaning non-blocking is now enabled (nonzero value enables it). Since you have non-blocking mode on, this will prevent the recv() function from halting your program. '/ IF ioctlsocket(Our_Sockets(This_Socket).sSocket, FIONBIO, @NonBlocking) = -1 THEN EXIT FUNCTION Our_Sockets(This_Socket).State = Socket_Is_Open ' Now tell it that the socket is open. Make_Socket_Open = -1 END FUNCTION FUNCTION Make_Socket_Receive (BYREF This_Socket AS UBYTE) AS INTEGER DIM zsBuffer AS ZSTRING * (Socket_Buffer_Get + 1) DIM BytesReceived AS INTEGER ' Maximum amount to grab at a time. BytesReceived = Socket_Buffer_Get ' Running out of buffer, let's roll it over. IF LEN(Our_Sockets(This_Socket).RecvBuffer) + Socket_Buffer_Get > SIZEOF(Our_Sockets(This_Socket).RecvBuffer) THEN BytesReceived = SIZEOF(Our_Sockets(This_Socket).RecvBuffer) - LEN(Our_Sockets(This_Socket).RecvBuffer) - 1 IF BytesReceived < 1 THEN EXIT FUNCTION END IF ' Winsock's routine to receive data BytesReceived = Recv(Our_Sockets(This_Socket).sSocket, @zsBuffer, Socket_Buffer_Get, 0) ' Data has been received! Now let's put it into our buffer! IF BytesReceived > 0 THEN Our_Sockets(This_Socket).RecvBuffer = Our_Sockets(This_Socket).RecvBuffer + zsBuffer Our_Sockets(This_Socket).RecvBytes = Our_Sockets(This_Socket).RecvBytes + BytesReceived END IF ' This will return non-zero for data being received. Make_Socket_Receive = BytesReceived END FUNCTION FUNCTION Make_Socket_Resolve (BYVAL stHostName AS STRING) AS INTEGER ' Internet address and host entry header. DIM ia AS in_addr DIM hostentry AS hostent PTR ' Check if it's an actual ip address ia.S_addr = inet_addr(stHostName) ' This converts a string containing an IPv4 dotted-decimal ' address into a proper address for the IN_ADDR structure. IF ia.S_addr = INADDR_NONE THEN ' IF not, assume it's a name. Use Winsock's routine to resolve it. hostentry = gethostbyname(stHostName) IF hostentry = 0 THEN EXIT FUNCTION Make_Socket_Resolve = *CAST(INTEGER PTR, *hostentry->h_addr_list) ELSE ' Just return the address Make_Socket_Resolve = ia.S_addr END IF END FUNCTION FUNCTION Make_Socket_Send (BYREF This_Socket AS UBYTE) AS INTEGER DIM BytesToSend AS INTEGER DIM zsBuffer AS ZSTRING * (Socket_Buffer_Get + 1) ' Get length BytesToSend = LEN(Our_Sockets(This_Socket).SendBuffer) IF BytesToSend = 0 THEN EXIT FUNCTION ' Truncate data to maxmimum get IF BytesToSend > Socket_Buffer_Get THEN BytesToSend = Socket_Buffer_Get ' Grab a small chunk of our data. zsBuffer = LEFT(Our_Sockets(This_Socket).SendBuffer, BytesToSend) ' Update the buffer's string and length. Our_Sockets(This_Socket).SendBuffer = MID(Our_Sockets(This_Socket).SendBuffer, BytesToSend + 1) Our_Sockets(This_Socket).SendBytes = Our_Sockets(This_Socket).SendBytes + BytesToSend ' Tell Winsock to send our small chunk of data. BytesToSend = Send(Our_Sockets(This_Socket).sSocket, @zsBuffer, BytesToSend, 0) Make_Socket_Send = BytesToSend END FUNCTION FUNCTION Poll_Our_Sockets ' Winsock has their own DATA types. DIM read_fd_set AS fd_set ' sSocket ID and counter will be utilized by these. DIM write_fd_set AS fd_set DIM except_fd_set AS fd_set DIM TimeOutValues AS timeval ' We'll use zero since we're in non-blocking mdoe. DIM Dummy AS INTEGER DIM iTemp AS INTEGER ' Loop counter. DIM This_Socket AS BYTE ' Checking our used sockets counter. DIM Second_Socket AS BYTE ' When accepting a connection, the second socket will be a copy of the listening socket. ' We will check all the sockets for any active operations. FOR This_Socket = 1 TO UBOUND(Our_Sockets) ' If a socket is listening, connecting, connected, or closing, then add them to the list. CheckThis = 0 SELECT CASE Our_Sockets(This_Socket).State CASE Socket_Is_Listening: CheckThis = 1 CASE Socket_Is_Connecting: CheckThis = 1 CASE Socket_Is_Connected: CheckThis = 1 CASE Socket_Is_Closing: CheckThis = 1 END SELECT /' When a socket needs to be checked, we add it to the list of sockets to be read and polled later on by SelectSocket(). '/ IF CheckThis THEN ' Add the active socket to the list. read_fd_set.fd_array(read_fd_set.fd_count) = Our_Sockets(This_Socket).sSocket read_fd_set.fd_count = read_fd_set.fd_count + 1 write_fd_set.fd_array(write_fd_set.fd_count) = Our_Sockets(This_Socket).sSocket write_fd_set.fd_count = write_fd_set.fd_count + 1 except_fd_set.fd_array(except_fd_set.fd_count) = Our_Sockets(This_Socket).sSocket except_fd_set.fd_count = except_fd_set.fd_count + 1 END IF NEXT This_Socket ' IF there's nothing to poll, then leave this routine! IF read_fd_set.fd_count = 0 AND write_fd_set.fd_count = 0 AND except_fd_set.fd_count = 0 THEN EXIT FUNCTION /' When you're polling your sockets, you'll end up checking all possible read, write, and error operations that have happened. Winsock's internal routines will capture all the current r/w/e updates, as long as we keep calling SelectSocket. This is where all the read/write/error polling happens! '/ Dummy = SelectSocket(read_fd_set.fd_count, @read_fd_set, @write_fd_set, @except_fd_set, @TimeOutValues) ' Debugging purposes. Sometimes, we want to know if our sockets are reading, writing, or having errors, at all! DummyStringA$ = "READ: " + STR(read_fd_set.fd_count) + " WRITE: " + STR(write_fd_set.fd_count) + " EXCEPT: " + STR(except_fd_set.fd_count ) ' IF return value is zero, no changes IF Dummy = 0 THEN EXIT FUNCTION ' IF it is less than zero, something happened IF Dummy < 0 THEN Poll_Our_Sockets = -1 ' Check readability of sockets FOR iTemp = 0 TO (read_fd_set.fd_count - 1) ' Remember this routine? Let's find which socket handle needs polling by its sSocket ID. This_Socket = Socket_Index(read_fd_set.fd_array(iTemp)) IF This_Socket > -1 THEN ' Socket is readable. /' Since the socket is now readable, let's make it to where... 1) A listening socket accepts an incoming connection. 2) A connecting socket becomes a connected socket. 3) A connected socket grabbing any possible incoming data. 4) A closing socket becomes a closed socket. '/ SELECT CASE Our_Sockets(This_Socket).State CASE Socket_Is_Listening Dummy = Make_Socket_Accept(This_Socket, Second_Socket) CASE Socket_Is_Connecting Our_Sockets(This_Socket).State = Socket_Is_Connected CASE Socket_Is_Connected Dummy = Make_Socket_Receive(This_Socket) IF Dummy = 0 THEN ' 0 bytes received, means remote peer closed connection Our_Sockets(This_Socket).State = Socket_Is_Closing END IF CASE Socket_Is_Closing IF LEN(Our_Sockets(This_Socket).RecvBuffer) = 0 THEN Dummy = Make_Socket_Close(This_Socket) ' IF receive buffer is empty, close socket END IF END SELECT END IF NEXT iTemp ' Check writability of sockets FOR iTemp = 0 TO (write_fd_set.fd_count - 1) This_Socket = Socket_Index(write_fd_set.fd_array(iTemp)) IF This_Socket > -1 THEN ' Socket is writable. /' Since the socket is now writable, let's make it to where... 1) A connecting socket becomes a connected socket. 2) A connected socket sends any available content. '/ SELECT CASE Our_Sockets(This_Socket).State CASE Socket_Is_Connecting Our_Sockets(This_Socket).State = Socket_Is_Connected CASE Socket_Is_Connected iRetVal = Make_Socket_Send(This_Socket) END SELECT END IF NEXT iTemp ' Check exceptions of sockets FOR iTemp = 1 TO (except_fd_set.fd_count - 1) This_Socket = Socket_Index(except_fd_set.fd_array(iTemp)) IF This_Socket > -1 THEN ' Socket encountered an error. ' An error occured with the socket. Our_Sockets(This_Socket).State = Socket_Is_Error iRetVal = Make_Socket_Close(This_Socket) END IF NEXT iTemp END FUNCTION SUB PrintErrorEnd (ErrorMessage$) ' END Program with error. ' Here, you'd like to know why you got an error, so print it out on screen! CLS COLOR 15 WSACleanUp PRINT ErrorMessage$ PRINT "Error code: (IF applicable)", WSAGetLastError SLEEP END END SUB FUNCTION Socket_Index (BYREF sSocket AS SOCKET) AS BYTE DIM Check_This_Socket AS BYTE ' Look through our sockets and identify which one has our sSocket value. FOR Check_This_Socket = 1 TO UBOUND(Our_Sockets) IF Our_Sockets(Check_This_Socket).sSocket = sSocket THEN Socket_Index = Check_This_Socket EXIT FOR END IF NEXT Check_This_Socket ' Not found IF Check_This_Socket > UBOUND(Our_Sockets) THEN Socket_Index = -1 END FUNCTION FUNCTION Socket_New DIM Check_This_Socket AS BYTE ' Look through all of our sockets, and find one that's not used. FOR Check_This_Socket = 1 TO UBOUND(Our_Sockets) IF Our_Sockets(Check_This_Socket).State = Socket_Is_Closed THEN ' New unused socket found, use it Our_Sockets(Check_This_Socket).sSocket = 0 ' Making sure everything is NULL. Our_Sockets(Check_This_Socket).SendBuffer = "" Our_Sockets(Check_This_Socket).SendBytes = 0 Our_Sockets(Check_This_Socket).RecvBuffer = "" Our_Sockets(Check_This_Socket).RecvBytes = 0 Socket_New = Check_This_Socket ' Return the value of the unused socket that was found. EXIT FOR END IF NEXT Check_This_Socket END FUNCTION SUB Winsock_Close ' Winsock's shut down routine that cleans up everything associated with Winsock. WSACleanUp END END SUB SUB Winsock_Start ' Winsock's data structure has to be used to retrieve our version info. DIM MakeOurWSAData AS WSAdata ' Fire up Winsock! We're requesting v2.0 IF WSAStartup(MAKEWORD(2, 0), @MakeOurWSAData) THEN SELECT CASE WSAGetLastError CASE WSASYSNOTREADY PRINT "Underlying Network subsystem is not ready." CASE WSAVERNOTSUPPORTED PRINT "The requested version is not supported." CASE WSAEINPROGRESS PRINT "A blocking Windows Sockets 1.1 operation is in progress." CASE WSAEPROCLIM PRINT "Winsock's usage has reached its limits by other programs." CASE WSAEFAULT PRINT "The second parameter is not a valid WSAData type." CASE ELSE PRINT "Unknown error. Error code (if any): ", WSAGetLastError END SELECT SLEEP END END IF /' Even though we know that 2.0 (or higher) is supported, we will go ahead and check the highest version you have available. You may now use the returned values from MakeOurWSAData to pull up the highest available version of Winsock. '/ OurVersionMajor$ = LTRIM(STR(MakeOurWSAData.wHighVersion AND 255)) OurVersionMinor$ = LTRIM(STR(MakeOurWSAData.wHighVersion SHR 8)) WinsockVersion$ = "v" + OurVersionMajor$ + "." + OurVersionMinor$ END SUB